package org.xins.server;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import javax.servlet.ServletConfig;
import org.apache.log4j.LogManager;
import org.apache.log4j.PropertyConfigurator;
import org.apache.log4j.helpers.NullEnumeration;
import org.xins.common.MandatoryArgumentChecker;
import org.xins.common.Utils;
import org.xins.common.collections.InvalidPropertyValueException;
import org.xins.common.collections.MapStringUtils;
import org.xins.common.collections.StatsMap;
import org.xins.common.collections.UniqueProperties;
import org.xins.common.io.FileWatcher;
import org.xins.common.io.HTTPFileWatcher;
import org.xins.common.text.TextUtils;
import org.znerd.logdoc.UnsupportedLocaleException;
import org.znerd.logdoc.log4j.Log4jLogBridge;
final class ConfigManager {
static final String CONFIG_FILE_SYSTEM_PROPERTY = "org.xins.server.config";
static final String CONFIG_RELOAD_INTERVAL_PROPERTY = "org.xins.server.config.reload";
static final String CONFIG_INCLUDE_PROPERTY = "org.xins.server.config.include";
static final String INIT_LOGGING_SYSTEM_PROPERTY = "org.xins.server.logging.init";
static final String CONTEXT_ID_PUSH_PROPERTY = "org.xins.server.contextID.push";
static final int DEFAULT_CONFIG_RELOAD_INTERVAL = 5;
static final String LOG_LOCALE_PROPERTY = "org.xins.logdoc.locale";
static final String LOG_STACK_TRACE_AT_MESSAGE_LEVEL = "org.xins.logdoc.stackTraceAtMessageLevel";
static final String DEFAULT_LOCALE = "en_US";
private static final Object RUNTIME_PROPERTIES_LOCK = new Object();
private final Engine _engine;
private final ServletConfig _config;
private final ConfigurationFileListener _configFileListener;
private String _configFile;
private String[] _configFiles;
private String _configFilesPath;
private FileWatcher _configFileWatcher;
private StatsMap<String, String> _runtimeProperties;
private boolean _propertiesRead;
ConfigManager(Engine engine, ServletConfig config)
throws IllegalArgumentException {
MandatoryArgumentChecker.check("engine", engine, "config", config);
_engine = engine;
_config = config;
_configFileListener = new ConfigurationFileListener();
}
static void configureLoggerFallback() {
Properties settings = new Properties();
settings.setProperty("log4j.rootLogger", "ALL, console");
settings.setProperty("log4j.appender.console", "org.apache.log4j.ConsoleAppender");
settings.setProperty("log4j.appender.console.layout", "org.apache.log4j.PatternLayout");
settings.setProperty("log4j.appender.console.layout.ConversionPattern", "%6c{1} %-6p %x %m%n");
settings.setProperty("log4j.logger.org.xins.", "INFO");
PropertyConfigurator.configure(settings);
}
void determineConfigFile() {
String configFile = null;
try {
configFile = System.getProperty(CONFIG_FILE_SYSTEM_PROPERTY + '.' + _engine.getApiName());
} catch (SecurityException exception) {
Log.log_3230(exception, CONFIG_FILE_SYSTEM_PROPERTY + '.' + _engine.getApiName());
}
try {
configFile = System.getProperty(CONFIG_FILE_SYSTEM_PROPERTY);
} catch (SecurityException exception) {
Log.log_3230(exception, CONFIG_FILE_SYSTEM_PROPERTY);
}
if (configFile == null || configFile.length() < 1) {
Log.log_3231(CONFIG_FILE_SYSTEM_PROPERTY);
configFile = _config.getInitParameter(CONFIG_FILE_SYSTEM_PROPERTY);
}
_configFile = configFile;
}
void readRuntimeProperties() {
UniqueProperties properties = new UniqueProperties();
InputStream in = null;
if (_configFile == null) {
in = _engine.getResourceAsStream("/WEB-INF/xins.properties");
if (in == null) {
Log.log_3205(CONFIG_FILE_SYSTEM_PROPERTY);
_runtimeProperties = null;
_propertiesRead = true;
return;
} else {
Log.log_3248();
}
}
_configFilesPath = _configFile;
synchronized (ConfigManager.RUNTIME_PROPERTIES_LOCK) {
try {
if (in != null) {
properties.load(in);
in.close();
} else if (!_configFile.startsWith("http://") && !_configFile.startsWith("https://")) {
_configFile = _configFile.replace('/', File.separatorChar);
_configFile = _configFile.replace('\\', File.separatorChar);
properties = readLocalRuntimeProperties();
} else {
properties = readHTTPRuntimeProperties();
}
_propertiesRead = true;
} catch (SecurityException exception) {
Log.log_3302(exception, _configFilesPath);
} catch (FileNotFoundException exception) {
String detail = TextUtils.trim(exception.getMessage(), null);
Log.log_3301(_configFilesPath, detail);
} catch (IOException exception) {
Log.log_3303(exception, _configFilesPath);
}
Map<String, String> pr = MapStringUtils.fromProperties(properties);
_runtimeProperties = new StatsMap<String, String>(pr);
if (getBooleanProperty(INIT_LOGGING_SYSTEM_PROPERTY, true)) {
Log.log_3300(_configFilesPath);
configureLogger(properties);
}
if (!properties.isUnique()) {
Log.log_3311(_configFilesPath);
_propertiesRead = false;
}
}
}
private UniqueProperties readLocalRuntimeProperties() throws IOException {
UniqueProperties properties = new UniqueProperties();
InputStream in = null;
try {
String fullPath = new File(_configFile).getAbsolutePath();
if (!_configFile.equals(fullPath)) {
_configFilesPath += " (full path: " + fullPath + ")";
}
in = new FileInputStream(_configFile);
properties.load(in);
if (properties.getProperty(CONFIG_INCLUDE_PROPERTY) != null &&
!properties.getProperty(CONFIG_INCLUDE_PROPERTY).trim().equals("")) {
StringTokenizer stInclude = new StringTokenizer(properties.getProperty(CONFIG_INCLUDE_PROPERTY), ",");
File baseFile = new File(_configFile).getParentFile();
_configFiles = new String[stInclude.countTokens() + 1];
_configFiles[0] = _configFile;
_configFilesPath += " + [";
int i = 0;
while (stInclude.hasMoreTokens()) {
String nextInclude = stInclude.nextToken().trim().replace('/', File.separatorChar).replace('\\', File.separatorChar);
File includeFile = new File(baseFile, nextInclude);
FileInputStream isInclude = new FileInputStream(includeFile);
properties.load(isInclude);
isInclude.close();
_configFiles[i + 1] = nextInclude;
_configFilesPath += nextInclude + ";";
i++;
}
_configFilesPath += "]";
} else {
_configFiles = new String[1];
_configFiles[0] = _configFile;
}
} finally {
if (in != null) {
try {
in.close();
} catch (Throwable exception) {
Utils.logIgnoredException(exception);
}
}
}
return properties;
}
private UniqueProperties readHTTPRuntimeProperties() throws IOException {
UniqueProperties properties = new UniqueProperties();
InputStream in = null;
try {
URL configURL = new URL(_configFile);
in = configURL.openStream();
properties.load(in);
if (properties.getProperty(CONFIG_INCLUDE_PROPERTY) != null &&
!properties.getProperty(CONFIG_INCLUDE_PROPERTY).trim().equals("")) {
StringTokenizer stInclude = new StringTokenizer(properties.getProperty(CONFIG_INCLUDE_PROPERTY), ",");
_configFiles = new String[stInclude.countTokens() + 1];
_configFiles[0] = _configFile;
_configFilesPath += " + [";
int i = 0;
while (stInclude.hasMoreTokens()) {
String nextInclude = stInclude.nextToken().trim().replace('/', File.separatorChar).replace('\\', File.separatorChar);
URL includeFile = new URL(configURL, nextInclude);
InputStream isInclude = includeFile.openStream();
properties.load(isInclude);
isInclude.close();
_configFiles[i + 1] = nextInclude;
_configFilesPath += nextInclude + ";";
i++;
}
_configFilesPath += "]";
} else {
_configFiles = new String[1];
_configFiles[0] = _configFile;
}
} finally {
if (in != null) {
try {
in.close();
} catch (Throwable exception) {
Utils.logIgnoredException(exception);
}
}
}
return properties;
}
Map<String, String> getRuntimeProperties() {
if (_runtimeProperties == null) {
return Collections.EMPTY_MAP;
} else {
return _runtimeProperties;
}
}
void init() {
int interval = DEFAULT_CONFIG_RELOAD_INTERVAL;
if (_configFile != null) {
try {
interval = determineConfigReloadInterval();
} catch (InvalidPropertyValueException exception) {
}
}
boolean initialized = _engine.initAPI();
if (_configFile != null && interval > 0) {
startConfigFileWatcher(interval);
}
if (initialized) {
logUnusedRuntimeProperties();
Log.log_3415();
}
}
private void logUnusedRuntimeProperties() {
if (_runtimeProperties != null) {
Set<String> unused = _runtimeProperties.getUnused().keySet();
for (String unusedName : unused) {
if (!unusedName.startsWith("log4j.")) {
Log.log_3434(unusedName);
}
}
}
}
void startConfigFileWatcher(int interval)
throws IllegalStateException, IllegalArgumentException {
if (_configFile == null || _configFile.length() < 1) {
throw new IllegalStateException(
"Name of runtime configuration file not set.");
} else if (_configFileWatcher != null) {
throw new IllegalStateException(
"Runtime configuration file watcher exists.");
} else if (interval < 1) {
throw new IllegalArgumentException("interval (" + interval + ") < 1");
}
if (_configFile.startsWith("http://") ||_configFile.startsWith("https://")) {
_configFileWatcher = new HTTPFileWatcher(_configFiles, interval, _configFileListener);
} else {
_configFileWatcher = new FileWatcher(_configFiles, interval, _configFileListener);
}
_configFileWatcher.start();
}
void reloadPropertiesIfChanged() {
if (_configFileWatcher == null) {
_configFileListener.reinit();
} else {
synchronized (_configFileWatcher) {
_configFileWatcher.notifyAll();
}
}
}
void configureLogger(Properties properties)
throws IllegalArgumentException {
MandatoryArgumentChecker.check("properties", properties);
org.znerd.logdoc.Library.setLogBridge(Log4jLogBridge.getInstance());
LogManager.getLoggerRepository().resetConfiguration();
String apiLogger = properties.getProperty("log4j.rootLogger." + _config.getServletName());
if (apiLogger != null) {
properties.setProperty("log4j.rootLogger", apiLogger);
}
PropertyConfigurator.configure(properties);
Enumeration appenders =
LogManager.getLoggerRepository().getRootLogger().getAllAppenders();
if (appenders instanceof NullEnumeration) {
Log.log_3304(_configFilesPath);
configureLoggerFallback();
} else {
Log.log_3305();
}
}
int determineConfigReloadInterval()
throws InvalidPropertyValueException {
if (_configFile == null || _configFile.length() < 1) {
throw new IllegalStateException("Name of runtime configuration file not set.");
}
String prop = CONFIG_RELOAD_INTERVAL_PROPERTY;
String s = _runtimeProperties.get(prop);
int interval;
if (s != null && s.length() >= 1) {
try {
interval = Integer.parseInt(s);
if (interval < 0) {
Log.log_3409(_configFilesPath, prop, s);
throw new InvalidPropertyValueException(prop, s, "Negative value.");
} else {
Log.log_3410(_configFilesPath, s);
}
} catch (NumberFormatException nfe) {
Log.log_3409(_configFilesPath, prop, s);
throw new InvalidPropertyValueException(prop, s, "Not a 32-bit integer number.");
}
} else {
Log.log_3408(_configFilesPath, prop, DEFAULT_CONFIG_RELOAD_INTERVAL);
interval = DEFAULT_CONFIG_RELOAD_INTERVAL;
}
return interval;
}
boolean determineLogLocale() {
String newLocale = null;
if (_runtimeProperties != null) {
newLocale = _runtimeProperties.get(ConfigManager.LOG_LOCALE_PROPERTY);
}
if (newLocale != null) {
String currentLocale = org.znerd.logdoc.Library.getLocale();
if (! currentLocale.equals(newLocale)) {
Log.log_3306(currentLocale, newLocale);
try {
org.znerd.logdoc.Library.setLocale(newLocale);
Log.log_3307(currentLocale, newLocale);
} catch (UnsupportedLocaleException exception) {
Log.log_3308(currentLocale, newLocale);
return false;
}
}
} else {
org.znerd.logdoc.Library.useDefaultLocale();
}
return true;
}
boolean getBooleanProperty(String propName, boolean fallback) {
boolean value;
String propertyValue = _runtimeProperties.get(propName);
if (TextUtils.isEmpty(propertyValue)) {
value = fallback;
} else if (propertyValue.equals("true")) {
value = true;
} else if (propertyValue.equals("false")) {
value = false;
} else {
throw new IllegalStateException("Incorrect value for the runtime property \"" + propName + "\" is \""
+ propertyValue + "\". It should be either \"true\" or \"false\" or empty.");
}
return value;
}
boolean propertiesRead() {
return _propertiesRead;
}
void destroy() {
if (_configFileWatcher != null) {
try {
_configFileWatcher.end();
} catch (Throwable exception) {
Utils.logIgnoredException(exception);
}
_configFileWatcher = null;
}
}
private final class ConfigurationFileListener implements FileWatcher.Listener {
private ConfigurationFileListener() {
}
private void reinit() {
if (_configFile != null) {
Log.log_3407(_configFile);
} else {
Log.log_3407("/WEB-INF/xins.properties");
}
boolean reinitialized;
synchronized (RUNTIME_PROPERTIES_LOCK) {
readRuntimeProperties();
reinitialized = _engine.initAPI();
updateFileWatcher();
}
if (reinitialized) {
logUnusedRuntimeProperties();
Log.log_3415();
}
}
private void updateFileWatcher() {
if (_configFileWatcher == null) {
return;
}
int newInterval;
try {
newInterval = determineConfigReloadInterval();
} catch (InvalidPropertyValueException exception) {
return;
}
int oldInterval = _configFileWatcher.getInterval();
if (oldInterval != newInterval) {
if (newInterval == 0 && _configFileWatcher != null) {
_configFileWatcher.end();
_configFileWatcher = null;
} else if (newInterval > 0 && _configFileWatcher == null) {
if (_configFile.startsWith("http://") ||_configFile.startsWith("https://")) {
_configFileWatcher = new HTTPFileWatcher(_configFiles, newInterval, _configFileListener);
} else {
_configFileWatcher = new FileWatcher(_configFiles, newInterval, _configFileListener);
}
_configFileWatcher.start();
} else {
_configFileWatcher.setInterval(newInterval);
Log.log_3403(_configFilesPath, oldInterval, newInterval);
}
}
}
public void fileFound() {
reinit();
}
public void fileNotFound() {
Log.log_3400(_configFilesPath);
}
public void fileNotModified() {
}
public void securityException(SecurityException exception) {
Log.log_3401(exception, _configFilesPath);
}
public void fileModified() {
reinit();
}
}
}