/*
* Copyright (C) NetStruxr, Inc. All rights reserved.
*
* This software is published under the terms of the NetStruxr
* Public Software License version 0.5, a copy of which has been
* included with this distribution in the LICENSE.NPL file. */
package er.extensions.foundation;
import java.util.Enumeration;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.webobjects.appserver.WOApplication;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSBundle;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSNotification;
import com.webobjects.foundation.NSProperties;
import com.webobjects.foundation.NSSelector;
import er.extensions.ERXExtensions;
import er.extensions.eof.ERXConstant;
import er.extensions.logging.ERXLogger;
/**
* Handles rapid turnaround for system configuration as well as swizzling of the EOModel connection
* dictionaries.
* <h3>Placing configuration parameters</h3>
* You can provide the system configuration by the following ways:
* <p>
* Note: This is the standard way for WebObjects 5.x applications.
* <ul>
* <li><code>Properties</code> file under the Resources group of the
* application and framework project.
* It's a {@link java.util.Properties} file and Project Wonder's
* standard project templates include it. (The templates won't
* be available on some platforms at this moment.)</li>
*
* <li><code>WebObjects.properties</code> under the user home directory;
* same format to Properties.
* <p>
* Note that the user home directory depends on the user who
* launch the application. They may change between the
* development and deployment time.</li>
*
* <li>Command line arguments
* <p>
* For example: <code>-WOCachingEnabled false -com.webobjects.pid $$</code><br>
* Don't forget to put a dash "-" before the key.</li>
* </ul>
* <h3>Loading order of the configuration parameters</h3>
* When the application launches, configuration parameters will
* be loaded by the following order. ERXConfigurationManager tries
* to reload them by the exactly same order when one of those
* configuration files changes.
* <ol>
* <li>Properties in frameworks that the application links to</li>
* <li>Properties in the application</li>
* <li>WebObjects.properties under the home directory</li>
* <li>Command line arguments</li>
* </ol>
* If there is a conflicting parameter between the files and
* arguments, the latter one overrides the earlier one.
* <p>
* Note that the order between frameworks does not seems
* to be specified. You should not put conflicting parameters
* between framework Properties files. On the other hand,
* the application Properties should be always loaded after
* all framework Properties are loaded. You can safely
* override parameters on the frameworks from the applications
* Properties.
*
* <h3>Changing the connection dictionary</h3>
* To do this for Oracle you can either specify on a per model basis
* or on a global basis.
* <pre>
* <strong>Global:</strong>
* dbConnectServerGLOBAL = myDatabaseServer
* dbConnectUserGLOBAL = me
* dbConnectPasswordGLOBAL = secret
* dbConnectPluginGLOBAL = Oracle
* <strong>Per Model for say model ER:</strong>
* ER.DBServer = myDatabaseServer
* ER.DBUser = me
* ER.DBPassword = secret
* ER.DBPlugin = Oracle
*
* <strong>Openbase:</strong> same, with DBDatabase and DBHostname
*
* <strong>JDBC:</strong> same with dbConnectURLGLOBAL, or ER.DBURL
* </pre>
* <p>
* Prototypes can be swapped globally or per model either by
* hydrating an archived prototype entity for a file or from
* another entity.
*
* @property er.extensions.ERXConfigurationManager.PropertiesTouchFile if this property is set to a file name, the application will register for notifications of changes to that file and when that file is touched, the application will re-load properties.
*/
public class ERXConfigurationManager {
private static final Logger log = LoggerFactory.getLogger(ERXConfigurationManager.class);
/**
* Notification posted when the configuration is updated.
* The Java system properties is the part of the configuration.
*/
public static final String ConfigurationDidChangeNotification = "ConfigurationDidChangeNotification";
/** Configuration manager singleton */
static ERXConfigurationManager defaultManager = null;
private String[] _commandLineArguments;
private NSArray _monitoredProperties;
private Properties _defaultProperties;
private Properties _commandLineArgumentProperties;
private boolean _isInitialized = false;
private boolean _isRapidTurnAroundInitialized = false;
/** Private constructor to prevent instantiation from outside the class */
private ERXConfigurationManager() {
/* empty */
}
/**
* If set, touching this path will be used to signal a change to properties files.
*/
private static String propertiesTouchFile() {
return ERXProperties.stringForKey("er.extensions.ERXConfigurationManager.PropertiesTouchFile");
}
/**
* Returns the single instance of this class
*
* @return the configuration manager
*/
public static ERXConfigurationManager defaultManager() {
if (defaultManager == null)
defaultManager = new ERXConfigurationManager();
return defaultManager;
}
/**
* Returns the command line arguments.
* {@link er.extensions.appserver.ERXApplication#main(String[], Class)} sets this value.
*
* @return the command line arguments as a String[]
* @see #setCommandLineArguments
*/
public String[] commandLineArguments() {
return _commandLineArguments;
}
/**
* Returns the command line arguments as Properties.
* {@link er.extensions.appserver.ERXApplication#main(String[], Class)} sets this value.
*
* @return the command line arguments as a String[]
* @see #setCommandLineArguments(String[])
*/
public Properties commandLineArgumentProperties() {
return (Properties) _commandLineArgumentProperties.clone();
}
/**
* Returns the command line arguments as Properties.
* {@link er.extensions.appserver.ERXApplication#main(String[], Class)} sets this value.
*
* @return the command line arguments as a String[]
* @see #setCommandLineArguments(String[])
*/
public Properties defaultProperties() {
return (Properties) _defaultProperties.clone();
}
/**
* Sets the command line arguments.
* {@link er.extensions.appserver.ERXApplication#main(String[], Class)} will call this method
* when the application starts up.
*
* @see #commandLineArguments()
*/
public void setCommandLineArguments(String [] newCommandLineArguments) {
_commandLineArguments = newCommandLineArguments;
_defaultProperties = (Properties) NSProperties._getProperties().clone();
_commandLineArgumentProperties = ERXProperties.propertiesFromArgv(_commandLineArguments);
}
/**
* Initializes the configuration manager.
* The framework principal {@link ERXExtensions} calls
* this method when the ERExtensions framework is loaded.
*/
public void initialize() {
if (! _isInitialized) {
_isInitialized = true;
loadConfiguration();
}
}
private NSArray monitoredProperties() {
if( _monitoredProperties == null) {
_monitoredProperties = ERXProperties.pathsForUserAndBundleProperties();
}
return _monitoredProperties;
}
/**
* Sets up the system for rapid turnaround mode. It will watch the changes
* on Properties files in application and framework bundles and
* WebObjects.properties under the home directory. Rapid turnaround mode
* will only be enabled if there are such files available and system has
* WOCaching disabled.
*/
public void configureRapidTurnAround() {
if (_isRapidTurnAroundInitialized) return;
_isRapidTurnAroundInitialized = true;
if (WOApplication.application()!=null && WOApplication.application().isCachingEnabled()) {
log.info("WOCachingEnabled is true. Disabling the rapid turnaround for Properties files");
registerPropertiesTouchFiles();
return;
}
for (Enumeration e = monitoredProperties().objectEnumerator(); e.hasMoreElements();) {
String path = (String) e.nextElement();
registerForFileNotification(path, "updateSystemProperties");
}
}
private void registerPropertiesTouchFiles() {
String propertiesTouchFile = propertiesTouchFile();
if (propertiesTouchFile != null) {
String appNamePlaceHolder = "/{AppName}/";
int appNamePlaceHolderIndex = propertiesTouchFile.lastIndexOf(appNamePlaceHolder);
if (appNamePlaceHolderIndex == -1) {
registerForFileNotification(propertiesTouchFile, "updateAllSystemProperties");
}
else {
if (WOApplication.application() != null) {
StringBuilder appSpecificTouchFile = new StringBuilder();
appSpecificTouchFile.append(propertiesTouchFile.substring(0, appNamePlaceHolderIndex + 1));
appSpecificTouchFile.append(WOApplication.application().name());
appSpecificTouchFile.append(propertiesTouchFile.substring(appNamePlaceHolderIndex + appNamePlaceHolder.length() - 1));
registerForFileNotification(appSpecificTouchFile.toString(), "updateAllSystemProperties");
}
StringBuilder globalTouchFile = new StringBuilder();
globalTouchFile.append(propertiesTouchFile.substring(0, appNamePlaceHolderIndex + 1));
globalTouchFile.append(propertiesTouchFile.substring(appNamePlaceHolderIndex + appNamePlaceHolder.length()));
registerForFileNotification(globalTouchFile.toString(), "updateAllSystemProperties");
}
}
}
private void registerForFileNotification(String path, String callbackMethod) {
try {
ERXFileNotificationCenter.defaultCenter().addObserver(this,
new NSSelector(callbackMethod, ERXConstant.NotificationClassArray),
path);
log.debug("Registered: {}", path);
} catch (Exception ex) {
log.error("An exception occured while registering the observer for the "
+ "logging configuration file: {} {}", ex.getClass().getName(), ex.getMessage(), ex);
}
}
/**
* This will overlay the current system config files. It will then
* re-load the command line args.
*/
public void loadConfiguration() {
Properties systemProperties = System.getProperties();
systemProperties = applyConfiguration(systemProperties);
if (ERXProperties._useLoadtimeAppSpecifics) {
ERXSystem.updateProperties(systemProperties);
ERXProperties.transferPropertiesFromSourceToDest(systemProperties, System.getProperties());
}
else {
ERXProperties.transferPropertiesFromSourceToDest(systemProperties, System.getProperties());
ERXSystem.updateProperties();
}
ERXLogger.configureLoggingWithSystemProperties();
}
/**
* This will overlay the current system config files. It will then
* re-load the command line args.
*/
public Properties applyConfiguration(Properties systemProperties) {
return ERXProperties.applyConfiguration(systemProperties, commandLineArgumentProperties());
}
/**
* Updates the configuration from the current configuration and
* posts {@link #ConfigurationDidChangeNotification}. It also
* calls {@link er.extensions.logging.ERXLogger#configureLoggingWithSystemProperties()} to reconfigure
* the logging system.
* <p>
* The configuration files: Properties and WebObjects.properties
* files are reloaded to the Java system properties by the same
* order to the when the system starts up. Then the command line
* arguments will be applied to the properties again so that
* the configuration will be consistent during the application
* lifespan.
* <p>
* This method is called when rapid turnaround is enabled and one
* of the configuration files changes.
*
* @param n NSNotification object for the event (null means load all files)
*/
public synchronized void updateSystemProperties(NSNotification n) {
loadConfiguration();
}
public synchronized void updateAllSystemProperties(NSNotification notification) {
loadConfiguration();
}
public final static int WindowsOperatingSystem=1;
public final static int MacOSXOperatingSystem=2;
public final static int SolarisOperatingSystem=3;
public final static int UnknownOperatingSystem=3;
private int _operatingSystem=0;
public int operatingSystem() {
if (_operatingSystem==0) {
String osName=ERXSystem.getProperty("os.name").toLowerCase();
if (osName.indexOf("windows")!=-1) _operatingSystem=WindowsOperatingSystem;
else if (osName.indexOf("solaris")!=-1) _operatingSystem=SolarisOperatingSystem;
else if (osName.indexOf("macos")!=-1 || osName.indexOf("mac os")!=-1) _operatingSystem=MacOSXOperatingSystem;
else _operatingSystem=UnknownOperatingSystem;
}
return _operatingSystem;
}
protected String documentRoot;
/**
* Path to the web server's document root.
* This implementation tries first to resolve the
* <code>application.name()+ "DocumentRoot"</code> property value,
* then the <code>ERXDocumentRoot</code> property before
* getting the <code>DocumentRoot</code> key in your WebServerConfig.plist in the
* JavaWebObjects bundle.
* @return to the web server's document root.
*/
public String documentRoot() {
if (documentRoot == null) {
// for WebObjects.properties
documentRoot = ERXProperties.stringForKey(WOApplication.application().name() + "DocumentRoot");
if(documentRoot == null) {
// for command line and Properties
documentRoot = ERXProperties.stringForKey("ERXDocumentRoot");
if(documentRoot == null) {
// default value
NSDictionary dict = ERXDictionaryUtilities.dictionaryFromPropertyList("WebServerConfig", NSBundle.bundleForName("JavaWebObjects"));
if(dict != null)
documentRoot = (String)dict.objectForKey("DocumentRoot");
}
}
}
return documentRoot;
}
/** holds the host name */
protected String _hostName;
/**
* Gets the default host name for the current local host.
* @return host name or UnknownHost if the host is unknown.
*/
public String hostName() {
if (_hostName == null) {
try {
_hostName = java.net.InetAddress.getLocalHost().getHostName();
} catch (java.net.UnknownHostException ehe) {
log.warn("Caught unknown host exception.", ehe);
_hostName = "UnknownHost";
}
}
return _hostName;
}
/**
* Checks if the application <del>is</del> may be deployed as a servlet.
* <p>
* The current implementation only checks if the application
* is linked against <code>JavaWOJSPServlet.framework</code>.
*
* @return true if the application is deployed as a servlet
*/
public boolean isDeployedAsServlet() {
NSArray frameworkNames = (NSArray)NSBundle.frameworkBundles().valueForKey("name");
return frameworkNames.containsObject("JavaWOJSPServlet");
}
}