ClientProperties.java

/**
 * ClientProperties.java This file is part of WattDepot.
 *
 * Copyright (C) 2013  Cam Moore
 *
 * 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 org.wattdepot.client;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Map;
import java.util.Properties;
import java.util.TreeMap;
import java.util.logging.Logger;

import org.wattdepot.common.util.UserHome;
import org.wattdepot.server.ServerProperties;

/**
 * ClientProperties - Provides access to the values stored in the
 * wattdepot-client.properties file.
 * 
 * @author Cam Moore
 * 
 */
public class ClientProperties {
  /** The full path to the client's home directory. */
  public static final String CLIENT_HOME_DIR = "wattdepot-client.homedir";
  /** Name of property used to store the username. */
  public static final String USER_NAME = "wattdepot-client.user.name";
  /** Name of property used to store the password. */
  public static final String USER_PASSWORD = "wattdepot-client.user.password";
  /** The wattdepot server host. */
  public static final String WATTDEPOT_SERVER_HOST = "wattdepot-server.hostname";
  /** The wattdepot server port key. */
  public static final String PORT_KEY = "wattdepot-server.port";
  /** The wattdepot server port key during testing. */
  public static final String TEST_PORT_KEY = "wattdepot-server.test.port";
  /** The logging level key. */
  public static final String LOGGING_LEVEL_KEY = "wattdepot-client.logging.level";

  /** Where we store the properties. */
  private Properties properties;

  /**
   * Creates a new ClientProperties instance using the default filename. Prints
   * an error to the console if problems occur on loading.
   */
  public ClientProperties() {
    this(null);
  }

  /**
   * Creates a new ClientProperties instance loaded from the given directory
   * name. Prints an error to the console if problems occur on loading.
   * 
   * @param clientSubdir
   *          The name of the subdirectory used to store all files for the
   *          client.
   */
  public ClientProperties(String clientSubdir) {
    try {
      initializeProperties(clientSubdir);
    }
    catch (Exception e) {
      Logger.getLogger("org.wattdepot.properties").info(
          "Error initializing client properties. " + e.getMessage());
    }
  }

  /**
   * 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 = (String) entry.getKey();
      String propValue = (String) entry.getValue();
      alphaProps.put(propName, propValue);
    }
    StringBuffer buff = new StringBuffer(25);
    buff.append("Server Properties:").append(cr);
    for (String key : alphaProps.keySet()) {
      if (key.contains("password")) {
        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);
  }

  /**
   * Sets the properties to their "test" equivalents and updates the system
   * properties.
   */
  public void setTestProperties() {
    properties.setProperty(PORT_KEY, properties.getProperty(TEST_PORT_KEY));
    trimProperties(properties);
    // update the system properties object to reflect these new values.
    Properties systemProperties = System.getProperties();
    systemProperties.putAll(properties);
    System.setProperties(systemProperties);

  }

  /**
   * Reads in the properties in wattdepot-client.properties if the 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-client.".
   * 
   * @param clientSubdir
   *          The name of the subdirectory used to store all files for this
   *          client.
   * @throws Exception
   *           if there is a problem.
   */
  private void initializeProperties(String clientSubdir) throws Exception {
    Logger logger = Logger.getLogger("org.wattdepot.properties");
    String userHome = UserHome.getHomeString();
    String wattDepot3Home = userHome + "/.wattdepot3/";
    String clientHome = null;
    if (clientSubdir == null) {
      clientHome = wattDepot3Home + "client";
    }
    else {
      clientHome = wattDepot3Home + clientSubdir;
    }
    String propFileName = clientHome + "/wattdepot-client.properties";
    String defaultServerHost = "localhost";
    String defaultPort = "8192";
    String defaultTestPort = "8194";
    this.properties = new Properties();
    // Set default values
    properties.setProperty(CLIENT_HOME_DIR, clientHome);
    properties.setProperty(WATTDEPOT_SERVER_HOST, defaultServerHost);
    properties.setProperty(PORT_KEY, defaultPort);
    properties.setProperty(TEST_PORT_KEY, defaultTestPort);
    properties.setProperty(LOGGING_LEVEL_KEY, "INFO");
    // Use properties from file, if they exist.
    FileInputStream stream = null;
    try {
      stream = new FileInputStream(propFileName);
      properties.load(stream);
      logger.info("Loading Client properties from: " + propFileName);
    }
    catch (IOException e) {
      logger.info(propFileName + " not found. Using default client properties.");
    }
    finally {
      if (stream != null) {
        stream.close();
      }
    }
    // grab all of the properties in the environment
    // 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(ServerProperties.ADMIN_USER_NAME_ENV)) {
        properties.setProperty(USER_NAME, prop.getValue());
      }
      if (prop.getKey().startsWith(ServerProperties.ADMIN_USER_PASSWORD_ENV)) {
        properties.setProperty(USER_PASSWORD, prop.getValue());
      }
    }
    addClientSystemProperties(this.properties);
    trimProperties(properties);
  }

  /**
   * Finds any System properties whose key begins with "wattdepot-server.", and
   * adds those key-value pairs to the passed Properties object.
   * 
   * @param properties
   *          The properties instance to be updated with the WattDepot system
   *          properties.
   */
  private void addClientSystemProperties(Properties properties) {
    Properties systemProperties = System.getProperties();
    for (Map.Entry<Object, Object> entry : systemProperties.entrySet()) {
      String sysPropName = (String) entry.getKey();
      if (sysPropName.startsWith("wattdepot-client.")) {
        String sysPropValue = (String) entry.getValue();
        properties.setProperty(sysPropName, sysPropValue);
      }
    }
  }

  /**
   * 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.
   * 
   * @param properties
   *          The properties.
   */
  private void trimProperties(Properties properties) {
    // 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();
      properties.setProperty(propName, properties.getProperty(propName).trim());
    }
  }

}