ServerProperties.java
/**
* ServerProperties.java This file is part of WattDepot.
* <p/>
* Copyright (C) 2013 Cam Moore
* <p/>
* 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.
* <p/>
* 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.
* <p/>
* 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 org.wattdepot.server;
import org.wattdepot.common.domainmodel.UserInfo;
import org.wattdepot.common.domainmodel.UserPassword;
import org.wattdepot.common.util.UserHome;
import org.wattdepot.extension.WattDepotExtension;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.logging.Logger;
/**
* ServerProperties - Provides access to the values stored in the
* wattdepot-server.properties file.
*
* @author Cam Moore
*/
public class ServerProperties {
/**
* The full path to the server's home directory.
*/
public static final String SERVER_HOME_DIR = "wattdepot-server.homedir";
/**
* The hostname key.
*/
public static final String HOSTNAME_KEY = "wattdepot-server.hostname";
/**
* Name of property used to store the admin username.
*/
public static final String ADMIN_USER_NAME = "wattdepot-server.admin.name";
/**
* The environment variable for storing the admin's name.
*/
public static final String ADMIN_USER_NAME_ENV = "WATTDEPOT_ADMIN_NAME";
/**
* Name of property used to store the admin password.
*/
public static final String ADMIN_USER_PASSWORD = "wattdepot-server.admin.password";
/**
* The environment variable for storing the admin's password.
*/
public static final String ADMIN_USER_PASSWORD_ENV = "WATTDEPOT_ADMIN_PASSWORD";
/**
* The environment variable name that holds the salt used for encrypting
* passwords in WattDepot.
*/
public static final String WATTDEPOT_SALT_ENV = "WATTDEPOT_SALT";
/**
* The WattDepot implementation class.
*/
public static final String WATT_DEPOT_IMPL_KEY = "wattdepot-server.wattdepot.impl";
/**
* The wattdepot server port key.
*/
public static final String PORT_KEY = "wattdepot-server.port";
/**
* The context root key.
*/
public static final String CONTEXT_ROOT_KEY = "wattdepot-server.context.root";
/** The option to enable SSL. */
public static final String SSL = "wattdepot-server.ssl";
/** The path to the keystore holding the certificate. */
public static final String SSL_KEYSTORE_PATH = "wattdepot-server.ssl.keystore.path";
/** The password for the keystore. */
public static final String SSL_KEYSTORE_PASSWORD = "wattdepot-server.ssl.keystore.password";
/** The password for the key. */
public static final String SSL_KEYSTORE_KEY_PASSWORD = "wattdepot-server.ssl.keystore.key.password";
/** The type of keystore. */
public static final String SSL_KEYSTORE_TYPE = "wattdepot-server.ssl.keystore.type";
/** The database connection driver class. */
public static final String DB_CONNECTION_DRIVER = "wattdepot-server.db.connection.driver";
/**
* The database connection driver url.
*/
public static final String DB_CONNECTION_URL = "wattdepot-server.db.connection.url";
/**
* The database connection driver url environment variable.
*/
public static final String DB_CONNECTION_URL_ENV = "WATTDEPOT_DATABASE_URL";
/**
* The database url.
*/
public static final String DATABASE_URL = "wattdepot-server.database.url";
/**
* The database username.
*/
public static final String DB_USER_NAME = "wattdepot-server.db.username";
/**
* The database password.
*/
public static final String DB_PASSWORD = "wattdepot-server.db.password";
/**
* The database show sql.
*/
public static final String DB_SHOW_SQL = "wattdepot-server.db.show.sql";
/**
* The database drop&create tables. valid values are 'validate' | 'update' |
* 'create' | 'create-drop'.
*/
public static final String DB_TABLE_UPDATE = "wattdepot-server.db.update";
/**
* Enable logging in the server. Logging may hide some stacktraces. Should be
* True for production.
*/
public static final String ENABLE_LOGGING_KEY = "wattdepot-server.enable.logging";
/**
* Check the Session opens vs close.
*/
public static final String CHECK_SESSIONS = "wattdepot-server.check.sessions";
/**
* The logging level key.
*/
public static final String LOGGING_LEVEL_KEY = "wattdepot-server.logging.level";
/**
* Enable timing of Server operations.
*/
public static final String SERVER_TIMING_KEY = "wattdepot-server.enable.timing";
/**
* The WattDepot implementation class during testing.
*/
public static final String TEST_WATT_DEPOT_IMPL_KEY = "wattdepot-server.test.wattdepot.impl";
/**
* The wattdepot server port key during testing.
*/
public static final String TEST_PORT_KEY = "wattdepot-server.test.port";
/**
* Heroku key.
*/
public static final String USE_HEROKU_KEY = "wattdepot-server.heroku";
/**
* Heroku test key.
*/
public static final String TEST_HEROKU_KEY = "wattdepot-server.test.heroku";
/**
* The hostname for Heroku.
*/
public static final String HEROKU_HOSTNAME_KEY = "wattdepot-server.heroku.hostname";
/**
* The heroku database URL.
*/
public static final String HEROKU_DATABASE_URL_KEY = "wattdepot-server.heroku.db.url";
/**
* String for false.
*/
public static final String FALSE = "false";
/**
* String for true.
*/
public static final String TRUE = "true";
/**
* String for the beginning of the WattDepotServer extension definitions.
*/
public static final String WATTDEPOT_EXTENSION = "wattdepot-server.extension";
/**
* Property key that holds all the WattDepotServer extensions defined in the properties file.
*/
public static final String WATTDEPOT_EXTENSIONS = WATTDEPOT_EXTENSION + "s";
/**
* Where we store the properties.
*/
private Properties properties;
/**
* Creates a new ServerProperties instance using the default filename. Prints
* an error to the console if problems occur on loading.
*
* @throws Exception if errors occur.
*/
public ServerProperties() throws Exception {
this(null);
}
/**
* Creates a new ServerProperties instance loaded from the given filename.
* Prints an error to the console if problems occur on loading.
*
* @param serverSubdir The name of the subdirectory used to store all files
* for this server.
* @throws Exception if errors occur.
*/
public ServerProperties(String serverSubdir) throws Exception {
initializeProperties(serverSubdir);
}
/**
* Sets the properties to their "test" equivalents and updates the system
* properties.
*/
public void setTestProperties() {
properties.setProperty(PORT_KEY, properties.getProperty(TEST_PORT_KEY));
properties.setProperty(WATT_DEPOT_IMPL_KEY, properties.getProperty(TEST_WATT_DEPOT_IMPL_KEY));
// turn off logging during testing.
properties.setProperty(LOGGING_LEVEL_KEY, "SEVERE");
trimProperties();
// update the system properties object to reflect these new values.
Properties systemProperties = System.getProperties();
systemProperties.putAll(properties);
System.setProperties(systemProperties);
}
/**
* Returns a string containing all current properties in alphabetical order.
* Properties with the word "password" in their key list "((hidden))" rather
* than their actual value for security reasons. This is not super
* sophisticated - other properties such as database URLs might benefit from
* being hidden too - but it should work for now.
*
* @return A string with the properties.
*/
public String echoProperties() {
String cr = System.getProperty("line.separator");
String eq = " = ";
String pad = " ";
// Adding them to a treemap has the effect of alphabetizing them.
TreeMap<String, String> alphaProps = new TreeMap<String, String>();
for (Map.Entry<Object, Object> entry : this.properties.entrySet()) {
String propName = entry.getKey().toString();
String propValue = entry.getValue().toString();
alphaProps.put(propName, propValue);
}
StringBuffer buff = new StringBuffer(25);
buff.append("Server Properties:").append(cr);
for (String key : alphaProps.keySet()) {
if (key.contains("password") || key.contains("database.url")) {
buff.append(pad).append(key).append(eq).append("((hidden))").append(cr);
}
else {
buff.append(pad).append(key).append(eq).append(get(key)).append(cr);
}
}
return buff.toString();
}
/**
* Returns the value of the Server Property specified by the key.
*
* @param key Should be one of the public static final strings in this class.
* @return The value of the key, or null if not found.
*/
public String get(String key) {
return this.properties.getProperty(key);
}
/**
* Returns the instance.
*
* @param key The key.
* @return The Object associated with the key.
*/
public Object getPropertyInstance(Object key) {
return this.properties.get(key);
}
/**
* Sets the given property.
*
* @param key the key.
* @param value the value
*/
public void set(String key, String value) {
this.properties.setProperty(key, value);
}
/**
* Ensures that the there is no leading or trailing whitespace in the property
* values. The fact that we need to do this indicates a bug in Java's
* Properties implementation to me.
*/
public void trimProperties() {
// Have to do this iteration in a Java 5 compatible manner. no
// stringPropertyNames().
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
String propName = (String) entry.getKey();
if (properties.getProperty(propName) != null) {
properties.setProperty(propName, properties.getProperty(propName).trim());
}
}
}
/**
* Reads in the properties in ~/.wattdepot3/server/wattdepot-server.properties
* if this file exists, and provides default values for all properties not
* mentioned in this file. Will also add any pre-existing System properties
* that start with "wattdepot-server.".
*
* @param serverSubdir The name of the subdirectory used to store all files
* for this server.
* @throws Exception if errors occur.
*/
private void initializeProperties(String serverSubdir) throws Exception {
Logger logger = Logger.getLogger("org.wattdepot.properties");
String userHome = UserHome.getHomeString();
String wattDepot3Home = userHome + "/.wattdepot3/";
String serverHome = null;
if (serverSubdir == null) {
serverHome = wattDepot3Home + "server";
}
else {
serverHome = wattDepot3Home + serverSubdir;
}
String propFileName = serverHome + "/wattdepot-server.properties";
this.properties = new Properties();
// Use properties from file, if they exist.
FileInputStream stream = null;
try {
stream = new FileInputStream(propFileName);
properties.load(stream);
logger.info("Loading Server properties from: " + propFileName);
}
catch (IOException e) {
logger.info(propFileName + " not found. Using default server properties.");
}
finally {
if (stream != null) {
stream.close();
}
}
processDatabaseURL(properties.getProperty(DATABASE_URL));
processWattDepotExtensions(properties);
// processDatabaseURL(properties.getProperty(DB_CONNECTION_URL));
// grab all of the properties in the environment
Map<String, String> systemProps = System.getenv();
for (Map.Entry<String, String> prop : systemProps.entrySet()) {
if (prop.getKey().startsWith(ADMIN_USER_NAME_ENV)) {
properties.setProperty(ADMIN_USER_NAME, prop.getValue());
}
if (prop.getKey().startsWith(ADMIN_USER_PASSWORD_ENV)) {
properties.setProperty(ADMIN_USER_PASSWORD, prop.getValue());
}
if (prop.getKey().startsWith(DB_CONNECTION_URL_ENV)) {
processDatabaseURL(prop.getValue());
}
if (prop.getKey().startsWith("DATABASE_URL")) {
processDatabaseURL(prop.getValue());
}
}
if (!properties.containsKey(ADMIN_USER_NAME) || !properties.containsKey(ADMIN_USER_PASSWORD) || !properties.containsKey(DB_CONNECTION_URL)) {
StringBuilder sb = new StringBuilder();
if (!properties.containsKey(ADMIN_USER_NAME)) {
sb.append("WattDepot Admin name not set. ");
}
if (!properties.containsKey(ADMIN_USER_PASSWORD)) {
sb.append("WattDepot Admin password not set. ");
}
if (!(properties.containsKey(DB_CONNECTION_URL_ENV) || properties.containsKey("DATABASE_URL"))) {
sb.append("WattDepot database url not set. ");
}
throw new SecurityException(sb.toString());
}
if (properties.containsKey(SSL) && properties.getProperty(SSL).equals(TRUE)) {
StringBuilder sb = new StringBuilder();
if (!properties.containsKey(SSL_KEYSTORE_PASSWORD)) {
sb.append("Keystore password is not set. ");
}
if (!properties.containsKey(SSL_KEYSTORE_KEY_PASSWORD)) {
sb.append("Keystore key password is not set. ");
}
if (sb.length() != 0) {
throw new SecurityException(sb.toString());
}
}
UserInfo.ROOT.setUid(properties.getProperty(ADMIN_USER_NAME));
UserInfo.ROOT.setPassword(properties.getProperty(ADMIN_USER_PASSWORD));
UserPassword.ROOT.setUid(properties.getProperty(ADMIN_USER_NAME));
UserPassword.ROOT.setPassword(properties.getProperty(ADMIN_USER_PASSWORD));
String defaultWattDepotImpl = "org.wattdepot.server.depository.impl.hibernate.WattDepotPersistenceImpl";
// Set default values if not set
if (!properties.containsKey(SERVER_HOME_DIR)) {
properties.setProperty(SERVER_HOME_DIR, serverHome);
}
if (!properties.containsKey(WATT_DEPOT_IMPL_KEY)) {
properties.setProperty(WATT_DEPOT_IMPL_KEY, defaultWattDepotImpl);
}
if (!properties.containsKey(HOSTNAME_KEY)) {
properties.setProperty(HOSTNAME_KEY, "localhost");
}
if (!properties.containsKey(PORT_KEY)) {
properties.setProperty(PORT_KEY, "8192");
}
if (!properties.containsKey(SSL)) {
properties.setProperty(SSL, FALSE);
}
if (!properties.containsKey(SSL_KEYSTORE_PATH)) {
properties.setProperty(SSL_KEYSTORE_PATH, serverHome + "/wattdepot.jks");
}
if (!properties.containsKey(SSL_KEYSTORE_TYPE)) {
properties.setProperty(SSL_KEYSTORE_TYPE, "JKS");
}
if (!properties.containsKey(DB_CONNECTION_DRIVER)) {
properties.setProperty(DB_CONNECTION_DRIVER, "org.postgresql.Driver");
}
if (!properties.containsKey(DB_CONNECTION_URL)) {
properties.setProperty(DB_CONNECTION_URL, "jdbc:postgresql://localhost:5432/wattdepot");
}
if (!properties.containsKey(DB_SHOW_SQL)) {
properties.setProperty(DB_SHOW_SQL, FALSE);
}
if (!properties.containsKey(DB_TABLE_UPDATE)) {
properties.setProperty(DB_TABLE_UPDATE, "update");
}
if (!properties.containsKey(ENABLE_LOGGING_KEY)) {
properties.setProperty(ENABLE_LOGGING_KEY, TRUE);
}
if (!properties.containsKey(CHECK_SESSIONS)) {
properties.setProperty(CHECK_SESSIONS, FALSE);
}
if (!properties.containsKey(LOGGING_LEVEL_KEY)) {
properties.setProperty(LOGGING_LEVEL_KEY, "INFO");
}
if (!properties.containsKey(CONTEXT_ROOT_KEY)) {
properties.setProperty(CONTEXT_ROOT_KEY, "wattdepot");
}
if (!properties.containsKey(SERVER_TIMING_KEY)) {
properties.setProperty(SERVER_TIMING_KEY, FALSE);
}
if (!properties.containsKey(TEST_PORT_KEY)) {
properties.setProperty(TEST_PORT_KEY, "8194");
}
if (!properties.containsKey(TEST_WATT_DEPOT_IMPL_KEY)) {
properties.setProperty(TEST_WATT_DEPOT_IMPL_KEY, defaultWattDepotImpl);
}
if (!properties.containsKey(USE_HEROKU_KEY)) {
properties.setProperty(USE_HEROKU_KEY, FALSE);
}
if (!properties.containsKey(TEST_HEROKU_KEY)) {
properties.setProperty(TEST_HEROKU_KEY, FALSE);
}
logger.finest(echoProperties());
trimProperties();
logger.finest(echoProperties());
// get PORT and DATABASE_URL for heroku
String webPort = System.getenv("PORT");
if (webPort != null && !webPort.isEmpty()) {
properties.setProperty(PORT_KEY, webPort);
}
String databaseURL = System.getenv("DATABASE_URL");
if (databaseURL != null && !databaseURL.isEmpty()) {
URI dbUri = new URI(databaseURL);
String username = dbUri.getUserInfo().split(":")[0];
String password = dbUri.getUserInfo().split(":")[1];
String dbUrl = "jdbc:postgresql://" + dbUri.getHost() + dbUri.getPath();
properties.setProperty(DB_USER_NAME, username);
properties.setProperty(DB_PASSWORD, password);
properties.setProperty(DB_CONNECTION_URL, dbUrl);
}
// logger.severe(echoProperties());
}
/**
* Consolidates all the WattDepotServer extensions.
*
* @param properties The ServerProperties.
*/
private void processWattDepotExtensions(Properties properties) {
ArrayList<WattDepotExtension> extensions = new ArrayList<WattDepotExtension>();
for (Object key : properties.keySet()) {
if (key.toString().startsWith(WATTDEPOT_EXTENSION)) {
String extensionClass = properties.getProperty(key.toString());
try {
WattDepotExtension extension = (WattDepotExtension) Class.forName(extensionClass).newInstance();
extensions.add(extension);
}
catch (InstantiationException e) {
e.printStackTrace();
}
catch (IllegalAccessException e) {
e.printStackTrace();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
properties.put(WATTDEPOT_EXTENSIONS, extensions);
}
/**
* Parses the given database url to set the database username, password, and connection url.
*
* @param url the database url.
* @throws java.net.URISyntaxException if there is a problem with the url.
*/
private void processDatabaseURL(String url) throws URISyntaxException {
if (url != null && !url.isEmpty()) {
URI dbUri = new URI(url);
if (dbUri.getUserInfo() != null && dbUri.getUserInfo().indexOf(":") != -1) {
String username = dbUri.getUserInfo().split(":")[0];
String password = dbUri.getUserInfo().split(":")[1];
properties.setProperty(DB_USER_NAME, username);
properties.setProperty(DB_PASSWORD, password);
}
String dbUrl = "jdbc:postgresql://" + dbUri.getHost() + ":" + dbUri.getPort() + dbUri.getPath();
properties.setProperty(DB_CONNECTION_URL, dbUrl);
}
}
/**
* Returns the fully qualified host name, such as
* "http://localhost:9876/wattdepot/". Note, the String will end with "/", so
* there is no need to append another if you are constructing a URI.
*
* @return The fully qualified host name.
*/
public String getFullHost() {
// We have a special case for Heroku here, which leaves out the port number.
// This is needed
// because Heroku apps listen on a private port on localhost, but remote
// connections into
// the server always come on port 80. This causes problems because the port
// number is used
// in at least 3 places: by the Server to decide what port number to bind to
// (on Heroku this
// is the private port # given by the $PORT environment variable), the
// announced URL of the
// server to the public (always 80 on Heroku, though usually left out of URI
// to default to
// 80), and the URI of parent resources such as a Source in a SensorData
// resource (should be
// the public port on Heroku).
if (properties.getProperty(USE_HEROKU_KEY).equals(TRUE)
|| properties.getProperty(TEST_HEROKU_KEY).equals(TRUE)) {
return "http://" + get(HOSTNAME_KEY) + ":80/" + get(CONTEXT_ROOT_KEY) + "/";
}
else {
return "http://" + get(HOSTNAME_KEY) + ":" + get(PORT_KEY) + "/" + get(CONTEXT_ROOT_KEY)
+ "/";
}
}
}