/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */
package org.apache.geode.admin.jmx.internal;

import static org.apache.geode.admin.internal.InetAddressUtils.toHostString;
import static org.apache.geode.admin.internal.InetAddressUtilsWithLogging.validateHost;
import static org.apache.geode.distributed.ConfigurationProperties.BIND_ADDRESS;
import static org.apache.geode.distributed.ConfigurationProperties.CLUSTER_SSL_CIPHERS;
import static org.apache.geode.distributed.ConfigurationProperties.CLUSTER_SSL_ENABLED;
import static org.apache.geode.distributed.ConfigurationProperties.CLUSTER_SSL_PROTOCOLS;
import static org.apache.geode.distributed.ConfigurationProperties.CLUSTER_SSL_REQUIRE_AUTHENTICATION;
import static org.apache.geode.distributed.ConfigurationProperties.LOCATORS;
import static org.apache.geode.distributed.ConfigurationProperties.MCAST_ADDRESS;
import static org.apache.geode.distributed.ConfigurationProperties.MCAST_PORT;
import static org.apache.geode.distributed.ConfigurationProperties.TCP_PORT;
import static org.apache.geode.distributed.internal.DistributionConfig.DEFAULT_SSL_CIPHERS;
import static org.apache.geode.distributed.internal.DistributionConfig.DEFAULT_SSL_ENABLED;
import static org.apache.geode.distributed.internal.DistributionConfig.DEFAULT_SSL_PROTOCOLS;
import static org.apache.geode.distributed.internal.DistributionConfig.DEFAULT_SSL_REQUIRE_AUTHENTICATION;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.InetAddress;
import java.net.URL;
import java.util.Enumeration;
import java.util.Properties;
import java.util.StringTokenizer;

import org.apache.geode.GemFireIOException;
import org.apache.geode.admin.DistributedSystemConfig;
import org.apache.geode.admin.DistributionLocatorConfig;
import org.apache.geode.admin.internal.DistributedSystemConfigImpl;
import org.apache.geode.admin.jmx.Agent;
import org.apache.geode.admin.jmx.AgentConfig;
import org.apache.geode.internal.classloader.ClassPathLoader;
import org.apache.geode.internal.util.IOUtils;

/**
 * Provides the JMX Agent configuration properties.
 * <p>
 * Supports importing of persisted properties from an external configuration file.
 * <p>
 * Select values can also be overridden with command line arguments. See remarks on individual
 * properties for further information.
 * <p>
 * Extends and implements DistributedSystemConfig.
 *
 * @since GemFire 3.5 (in which it was named AgentConfig)
 */
@Deprecated
public class AgentConfigImpl extends DistributedSystemConfigImpl implements AgentConfig {

  // -------------------------------------------------------------------------
  // Static class variable(s)
  // -------------------------------------------------------------------------

  /**
   * Command-line arg to enable agent debugging
   */
  public static final String AGENT_DEBUG = "agent-debug";

  /**
   * The name of the "propertyFile" property. May specify as cmd-line arg
   */
  public static final String PROPERTY_FILE_NAME = "property-file";

  /**
   * The name of the "gfAgentPropertyFile" property, can be specified as System Property
   */
  public static final String AGENT_PROPSFILE_PROPERTY_NAME = "gfAgentPropertyFile";

  // -------------------------------------------------------------------------
  // DistributionLocator properties...
  // -------------------------------------------------------------------------

  /**
   * The name of the "locator.host-" property
   */
  public static final String LOCATOR_HOST_NAME = "locator.host-";
  /**
   * The name of the "locator.port-" property
   */
  public static final String LOCATOR_PORT_NAME = "locator.port-";
  /**
   * The name of the "locator.product-directory-" property
   */
  public static final String LOCATOR_PRODUCT_DIRECTORY_NAME = "locator.product-directory-";
  /**
   * The name of the "locator.working-directory-" property
   */
  public static final String LOCATOR_WORKING_DIRECTORY_NAME = "locator.working-directory-";
  /**
   * The name of the "locator.remote-command-" property
   */
  public static final String LOCATOR_REMOTE_COMMAND = "locator.remote-command-";
  /**
   * The name of the "locator.bind-address-" property
   */
  public static final String LOCATOR_BIND_ADDRESS = "locator.bind-address-";
  /**
   * the properties used in configuring a locator's distributed system
   */
  public static final String LOCATOR_DS_PROPERTIES = "locator.ds-properties";

  /**
   * The default log file for stand-alone JMX agents
   */
  /* package scope */
  static final String DEFAULT_LOG_FILE = "agent.log";

  /**
   * The default startup log file to be used by agent launcher
   */
  /* package scope */
  static final String DEFAULT_STARTUP_LOG_FILE = "start_agent.log";

  private static final String OBFUSCATED_STRING = "********";

  ////////////////////// Static Methods //////////////////////

  /**
   * The <code>propertyFile</code> is the name of the property file that will be loaded on startup
   * of the Agent.
   * <p>
   * The file will be searched for, in order, in the following directories:
   * <ol>
   * <li>the current directory
   * <li>the home directory
   * <li>the class path
   * </ol>
   * Only the first file found will be used.
   * <p>
   * The default value of propertyFile is <code>"agent.properties"</code>. However if the
   * "gfAgentPropertyFile" system property is set then its value is the value of propertyFile. If
   * this value is a relative file system path then the above search is done. If its an absolute
   * file system path then that file must exist; no search for it is done.
   */
  static String retrievePropertyFile() {
    return System.getProperty(AGENT_PROPSFILE_PROPERTY_NAME, DEFAULT_PROPERTY_FILE);
  }

  /**
   * Creates a new <code>Properties</code> object that contains all of the default values.
   */
  private static Properties getDefaultProperties() {
    Properties props = new Properties();

    props.setProperty(AUTO_CONNECT_NAME, String.valueOf(DEFAULT_AUTO_CONNECT));

    props.setProperty(HTTP_ENABLED_NAME, String.valueOf(DEFAULT_HTTP_ENABLED));
    props.setProperty(HTTP_BIND_ADDRESS_NAME, DEFAULT_HTTP_BIND_ADDRESS);
    props.setProperty(HTTP_PORT_NAME, String.valueOf(DEFAULT_HTTP_PORT));
    props.setProperty(HTTP_AUTHENTICATION_ENABLED_NAME,
        String.valueOf(DEFAULT_HTTP_AUTHENTICATION_ENABLED));
    props.setProperty(HTTP_AUTHENTICATION_USER_NAME,
        DEFAULT_HTTP_AUTHENTICATION_USER);
    props.setProperty(HTTP_AUTHENTICATION_PASSWORD_NAME,
        DEFAULT_HTTP_AUTHENTICATION_PASSWORD);

    props.setProperty(RMI_ENABLED_NAME, String.valueOf(DEFAULT_RMI_ENABLED));
    props.setProperty(RMI_REGISTRY_ENABLED_NAME, String.valueOf(DEFAULT_RMI_REGISTRY_ENABLED));
    props.setProperty(RMI_BIND_ADDRESS_NAME, DEFAULT_RMI_BIND_ADDRESS);
    props.setProperty(RMI_PORT_NAME, String.valueOf(DEFAULT_RMI_PORT));
    props.setProperty(RMI_SERVER_PORT_NAME, String.valueOf(DEFAULT_RMI_SERVER_PORT));

    props.setProperty(SNMP_ENABLED_NAME, String.valueOf(DEFAULT_SNMP_ENABLED));
    props.setProperty(SNMP_DIRECTORY_NAME, DEFAULT_SNMP_DIRECTORY);

    props.setProperty(AGENT_SSL_ENABLED_NAME, String.valueOf(DEFAULT_AGENT_SSL_ENABLED));
    props.setProperty(AGENT_SSL_PROTOCOLS_NAME, DEFAULT_AGENT_SSL_PROTOCOLS);
    props.setProperty(AGENT_SSL_CIPHERS_NAME, DEFAULT_AGENT_SSL_CIPHERS);
    props.setProperty(AGENT_SSL_REQUIRE_AUTHENTICATION_NAME,
        String.valueOf(DEFAULT_AGENT_SSL_REQUIRE_AUTHENTICATION));
    props.setProperty(HTTP_SSL_REQUIRE_AUTHENTICATION_NAME,
        String.valueOf(DEFAULT_HTTP_SSL_REQUIRE_AUTHENTICATION));

    return props;
  }

  /**
   * Returns default values for all valid agent properties as a Properties object.
   *
   * @return default values for all valid agent properties
   */
  static Properties getDefaultValuesForAllProperties() {
    Properties props = new Properties();

    props.setProperty(AUTO_CONNECT_NAME, String.valueOf(DEFAULT_AUTO_CONNECT));

    props.setProperty(HTTP_ENABLED_NAME, String.valueOf(DEFAULT_HTTP_ENABLED));

    props.setProperty(HTTP_BIND_ADDRESS_NAME, DEFAULT_HTTP_BIND_ADDRESS);

    props.setProperty(HTTP_PORT_NAME, String.valueOf(DEFAULT_HTTP_PORT));

    props.setProperty(HTTP_AUTHENTICATION_ENABLED_NAME,
        String.valueOf(DEFAULT_HTTP_AUTHENTICATION_ENABLED));

    props.setProperty(HTTP_AUTHENTICATION_USER_NAME,
        DEFAULT_HTTP_AUTHENTICATION_USER);

    props.setProperty(HTTP_AUTHENTICATION_PASSWORD_NAME,
        DEFAULT_HTTP_AUTHENTICATION_PASSWORD);

    props.setProperty(RMI_ENABLED_NAME, String.valueOf(DEFAULT_RMI_ENABLED));

    props.setProperty(RMI_REGISTRY_ENABLED_NAME, String.valueOf(DEFAULT_RMI_REGISTRY_ENABLED));

    props.setProperty(RMI_BIND_ADDRESS_NAME, DEFAULT_RMI_BIND_ADDRESS);

    props.setProperty(RMI_PORT_NAME, String.valueOf(DEFAULT_RMI_PORT));

    props.setProperty(RMI_SERVER_PORT_NAME, String.valueOf(DEFAULT_RMI_SERVER_PORT));

    props.setProperty(SNMP_ENABLED_NAME, String.valueOf(DEFAULT_SNMP_ENABLED));

    props.setProperty(SNMP_DIRECTORY_NAME, DEFAULT_SNMP_DIRECTORY);

    props.setProperty(AGENT_SSL_ENABLED_NAME, String.valueOf(DEFAULT_AGENT_SSL_ENABLED));

    props.setProperty(AGENT_SSL_PROTOCOLS_NAME, DEFAULT_AGENT_SSL_PROTOCOLS);

    props.setProperty(AGENT_SSL_CIPHERS_NAME, DEFAULT_AGENT_SSL_CIPHERS);

    props.setProperty(AGENT_SSL_REQUIRE_AUTHENTICATION_NAME,
        String.valueOf(DEFAULT_AGENT_SSL_REQUIRE_AUTHENTICATION));

    props.setProperty(HTTP_SSL_REQUIRE_AUTHENTICATION_NAME,
        String.valueOf(DEFAULT_HTTP_SSL_REQUIRE_AUTHENTICATION));

    props.setProperty(SNMP_BIND_ADDRESS_NAME, DEFAULT_SNMP_BIND_ADDRESS);

    props.setProperty(EMAIL_NOTIFICATIONS_ENABLED_NAME,
        String.valueOf(DEFAULT_EMAIL_NOTIFICATIONS_ENABLED));

    props.setProperty(EMAIL_NOTIFICATIONS_HOST_NAME, DEFAULT_EMAIL_HOST);

    props.setProperty(EMAIL_NOTIFICATIONS_FROM_NAME, DEFAULT_EMAIL_FROM);

    props.setProperty(EMAIL_NOTIFICATIONS_TO_LIST_NAME, DEFAULT_EMAIL_TO_LIST);

    props.setProperty(STATE_SAVE_FILE_NAME, DEFAULT_STATE_SAVE_FILE);

    props.setProperty(CLUSTER_SSL_ENABLED, String.valueOf(DEFAULT_SSL_ENABLED));

    props.setProperty(CLUSTER_SSL_PROTOCOLS, DEFAULT_SSL_PROTOCOLS);

    props.setProperty(CLUSTER_SSL_CIPHERS, DEFAULT_SSL_CIPHERS);

    props.setProperty(CLUSTER_SSL_REQUIRE_AUTHENTICATION,
        String.valueOf(DEFAULT_SSL_REQUIRE_AUTHENTICATION));

    props.setProperty(ENTITY_CONFIG_XML_FILE_NAME, DEFAULT_ENTITY_CONFIG_XML_FILE);

    props.setProperty(MCAST_PORT, String.valueOf(DEFAULT_MCAST_PORT));

    props.setProperty(MCAST_ADDRESS, String.valueOf(DEFAULT_MCAST_ADDRESS));

    props.setProperty(LOCATORS, DEFAULT_LOCATORS);

    props.setProperty(BIND_ADDRESS, DEFAULT_BIND_ADDRESS);

    props.setProperty(REMOTE_COMMAND_NAME, DEFAULT_REMOTE_COMMAND);

    props.setProperty(LOG_FILE_NAME, DEFAULT_LOG_FILE);

    props.setProperty(LOG_LEVEL_NAME, DEFAULT_LOG_LEVEL);

    props.setProperty(LOG_DISK_SPACE_LIMIT_NAME, String.valueOf(DEFAULT_LOG_DISK_SPACE_LIMIT));

    props.setProperty(LOG_FILE_SIZE_LIMIT_NAME, String.valueOf(DEFAULT_LOG_FILE_SIZE_LIMIT));

    props.setProperty(REFRESH_INTERVAL_NAME, String.valueOf(DEFAULT_REFRESH_INTERVAL));

    return props;

  }

  // -------------------------------------------------------------------------
  // Member variable(s)
  // -------------------------------------------------------------------------

  /**
   * Does agent automatically connect to the distributed system?
   */
  private boolean autoConnect;

  /**
   * True if Agent adaptors should use SSL
   */
  private boolean agentSSLEnabled;

  /**
   * The SSL Protocols that the Agent adaptors will use
   */
  private String agentSSLProtocols;

  /**
   * The SSL Ciphers that the Agent adaptors will use
   */
  private String agentSSLCiphers;

  /**
   * True if Agent adaptors require authentication when SSL is enabled
   */
  private boolean agentSSLRequireAuth;

  /**
   * HttpAdaptor won't work with ssl authentication required, so this attribute allows this option
   * to be turned on for RMI but off for HTTP in the event that both adaptors are being used with
   * ssl.
   */
  private boolean httpSSLRequireAuth;

  /**
   * True if HttpAdaptor authentication is enabled
   */
  private boolean httpAuthEnabled;

  /**
   * The login user for HttpAdaptor authentication
   */
  private String httpAuthUser;

  /**
   * The login password for HttpAdaptor authentication
   */
  private String httpAuthPassword;

  /**
   * True if the HttpAdaptor is enabled
   */
  private boolean httpEnabled;

  /**
   * The port for the MX4J HttpAdatper
   */
  private int httpPort;

  /**
   * The host for the MX4J HttpAdatper
   */
  private String httpBindAddress;

  /**
   * True if the RMIConnectorServer is enabled
   */
  private boolean rmiEnabled;

  /**
   * True if the Agent is to create its own RMI registry
   */
  private boolean rmiRegistryEnabled;

  /**
   * The host for the MX4J RMIConnectorServer
   */
  private String rmiBindAddress;

  /**
   * The port for the RMI Registry created by the Agent
   */
  private int rmiPort;

  /**
   * The port for the MX4J RMIConnectorServer
   */
  private int rmiServerPort;

  /**
   * True if the SnmpAdaptor is enabled
   */
  private boolean snmpEnabled;

  /**
   * The bind address for sockets used by the SNMP adapter
   */
  private String snmpBindAddress;

  /**
   * Path to the directory containing the SNMP Adaptor and its sub-dirs
   */
  private String snmpDirectory;

  /**
   * Is Email notification enabled
   */
  private boolean isEmailNotificationEnabled;

  /**
   * Email notification from: emailID
   */
  private String emailNotificationFrom;

  /**
   * The host name of the mail server to be used for email communication.
   */
  private String emailNotificationHostName;

  /**
   * Email notification to: emailIDs list
   */
  private String emailNotificationToList;

  /**
   * State Save File Name
   */
  private String stateSaveFile;

  /**
   * Describes the property file used to load configuration from. Null if no file was found.
   */
  private final URL url;

  /**
   * Original command line arguments
   */
  private String[] originalCmdLineArgs = null;

  /**
   * The <code>Agent</code> that is configured by this <code>AgentConfigImpl</code>
   */
  private Agent agent;

  // -------------------------------------------------------------------------
  // Constructor(s)
  // -------------------------------------------------------------------------

  /**
   * Constructs new instance of <code>AgentConfigImpl</code> with the default configuration.
   */
  public AgentConfigImpl() {
    this(getDefaultProperties());
  }

  /**
   * Constructs new instance of AgentConfig. Supplied command-line arguments are used to create a
   * set of non-default properties for initializing this AgentConfig.
   *
   * @param args array of non-default configuration arguments
   */
  public AgentConfigImpl(String[] args) {
    this(toProperties(args));
    originalCmdLineArgs = args;
  }

  /**
   * Creates a new <code>AgentConfig</code> with the given non-default configuration properties.
   *
   * @param props overriding non-default configuration properties
   */
  public AgentConfigImpl(Properties props) {
    // for admin bug #40434
    super(filterOutAgentProperties(appendOptionalPropertyFileProperties(props)),
        true/* ignore gemfire.properties */);

    // first get any property values set in the optional property file
    url = getPropertyFileURL(retrievePropertyFile());

    initialize(appendOptionalPropertyFileProperties(props));
  }

  /**
   * Constructs new instance of AgentConfig using the specified property file.
   *
   * @param propFile the file to load configuration properties from
   */
  public AgentConfigImpl(File propFile) {
    // Initialize default values
    this();

    Properties props = new Properties();
    if (propFile.exists()) {
      try {
        FileInputStream in = new FileInputStream(propFile);
        props.load(in);
        in.close();
      } catch (java.io.IOException e) {
        throw new GemFireIOException(
            String.format("Failed reading %s", propFile), e);
      }
    } else {
      throw new IllegalArgumentException(
          String.format("Specified properties file does not exist: %s",
              propFile));
    }

    initialize(props);
  }

  ////////////////////// Instance Methods //////////////////////

  /**
   * Sets the <code>Agent</code> associated with this <code>AgentConfigImpl</code>.
   */
  void setAgent(Agent agent) {
    this.agent = agent;
  }

  /**
   * Checks to see if this config object is "read only". If it is, then an
   * {@link IllegalStateException} is thrown.
   *
   * @since GemFire 4.0
   */
  @Override
  protected void checkReadOnly() {
    if (agent != null) {
      throw new IllegalStateException(
          "An AgentConfig object cannot be modified after it has been used to create an Agent.");
    }

    super.checkReadOnly();
  }

  // -------------------------------------------------------------------------
  // Methods for handling Properties and the Properties file
  // -------------------------------------------------------------------------

  /**
   * Returns a description of the property file used to load this config. If no property file was
   * used then the description will say so.
   */
  public String getPropertyFileDescription() {
    /*
     * Checking if the specified or the default properties file exists. If not, just log this as an
     * information.
     */
    if (url == null) {
      return "Using default configuration because property file was not found.";
    } else {
      return String.format("Configuration loaded from: %s.",
          url);
    }
  }

  /**
   * Returns the default property file that will be used when configuration is saved.
   */
  public File getPropertyFile() {
    File f;
    if (url == null) {
      f = new File(retrievePropertyFile());
      if (!f.isAbsolute()) {
        // save to <cwd>/propertyFile
        f = new File(System.getProperty("user.dir"), retrievePropertyFile());
      }
    } else {
      f = new File(url.getFile());
    }
    return f;
  }

  /**
   * Converts the contents of this config to a property instance and Stringifies it
   *
   * @return contents of this config as String
   */
  public String toPropertiesAsString() {
    Properties p = toProperties(true /* include DS properties */);

    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    pw.println("Agent Configuration:");
    Enumeration e = p.propertyNames();
    while (e.hasMoreElements()) {
      String pn = (String) e.nextElement();
      String pv = p.getProperty(pn);
      pw.println("  " + pn + " = " + pv);
    }
    pw.close();

    return sw.toString();
  }

  /**
   * Converts the contents of this config to a property instance.
   *
   * @return contents of this config as java.util.Properties
   */
  public Properties toProperties() {
    return toProperties(false /* include DS properties */);
  }

  /**
   * Converts the contents of this config to a property instance.
   *
   * @param includeDSProperties Should distributed system properties be included in the
   *        <code>Properties</code> object? See bug 32682.
   *
   * @return contents of this config as java.util.Properties
   */
  public Properties toProperties(boolean includeDSProperties) {
    Properties props = new Properties();

    props.setProperty(AUTO_CONNECT_NAME, toString(AUTO_CONNECT_NAME, getAutoConnect()));

    props.setProperty(HTTP_ENABLED_NAME, toString(HTTP_ENABLED_NAME, isHttpEnabled()));
    props.setProperty(HTTP_BIND_ADDRESS_NAME,
        toString(HTTP_BIND_ADDRESS_NAME, getHttpBindAddress()));
    props.setProperty(HTTP_PORT_NAME, toString(HTTP_PORT_NAME, getHttpPort()));

    props.setProperty(RMI_ENABLED_NAME, toString(RMI_ENABLED_NAME, isRmiEnabled()));
    props.setProperty(RMI_REGISTRY_ENABLED_NAME,
        toString(RMI_REGISTRY_ENABLED_NAME, isRmiRegistryEnabled()));
    props.setProperty(RMI_BIND_ADDRESS_NAME, toString(RMI_BIND_ADDRESS_NAME, getRmiBindAddress()));
    props.setProperty(RMI_PORT_NAME, toString(RMI_PORT_NAME, getRmiPort()));
    props.setProperty(RMI_SERVER_PORT_NAME, toString(RMI_SERVER_PORT_NAME, getRmiServerPort()));

    props.setProperty(SNMP_ENABLED_NAME, toString(SNMP_ENABLED_NAME, isSnmpEnabled()));
    props.setProperty(SNMP_BIND_ADDRESS_NAME,
        toString(SNMP_BIND_ADDRESS_NAME, getSnmpBindAddress()));
    props.setProperty(SNMP_DIRECTORY_NAME, toString(SNMP_DIRECTORY_NAME, getSnmpDirectory()));

    props.setProperty(AGENT_SSL_ENABLED_NAME,
        toString(AGENT_SSL_ENABLED_NAME, isAgentSSLEnabled()));
    props.setProperty(AGENT_SSL_PROTOCOLS_NAME,
        toString(AGENT_SSL_PROTOCOLS_NAME, getAgentSSLProtocols()));
    props.setProperty(AGENT_SSL_CIPHERS_NAME,
        toString(AGENT_SSL_CIPHERS_NAME, getAgentSSLCiphers()));
    props.setProperty(AGENT_SSL_REQUIRE_AUTHENTICATION_NAME,
        toString(AGENT_SSL_REQUIRE_AUTHENTICATION_NAME, isAgentSSLRequireAuth()));
    props.setProperty(HTTP_SSL_REQUIRE_AUTHENTICATION_NAME,
        toString(HTTP_SSL_REQUIRE_AUTHENTICATION_NAME, isHttpSSLRequireAuth()));

    props.setProperty(HTTP_AUTHENTICATION_ENABLED_NAME,
        toString(HTTP_AUTHENTICATION_ENABLED_NAME, isHttpAuthEnabled()));
    props.setProperty(HTTP_AUTHENTICATION_USER_NAME,
        toString(HTTP_AUTHENTICATION_USER_NAME, getHttpAuthUser()));
    props.setProperty(HTTP_AUTHENTICATION_PASSWORD_NAME,
        toString(HTTP_AUTHENTICATION_PASSWORD_NAME, getHttpAuthPassword()));

    props.setProperty(EMAIL_NOTIFICATIONS_ENABLED_NAME,
        toString(EMAIL_NOTIFICATIONS_ENABLED_NAME, isEmailNotificationEnabled()));
    props.setProperty(EMAIL_NOTIFICATIONS_HOST_NAME,
        toString(EMAIL_NOTIFICATIONS_HOST_NAME, getEmailNotificationHost()));
    props.setProperty(EMAIL_NOTIFICATIONS_FROM_NAME,
        toString(EMAIL_NOTIFICATIONS_FROM_NAME, getEmailNotificationFrom()));
    props.setProperty(EMAIL_NOTIFICATIONS_TO_LIST_NAME,
        toString(EMAIL_NOTIFICATIONS_TO_LIST_NAME, getEmailNotificationToList()));

    props.setProperty(STATE_SAVE_FILE_NAME, toString(STATE_SAVE_FILE_NAME, getStateSaveFile()));

    props.setProperty(EMAIL_NOTIFICATIONS_ENABLED_NAME,
        toString(EMAIL_NOTIFICATIONS_ENABLED_NAME, isEmailNotificationEnabled()));
    props.setProperty(EMAIL_NOTIFICATIONS_HOST_NAME,
        toString(EMAIL_NOTIFICATIONS_HOST_NAME, getEmailNotificationHost()));
    props.setProperty(EMAIL_NOTIFICATIONS_FROM_NAME,
        toString(EMAIL_NOTIFICATIONS_FROM_NAME, getEmailNotificationFrom()));
    props.setProperty(EMAIL_NOTIFICATIONS_TO_LIST_NAME,
        toString(EMAIL_NOTIFICATIONS_TO_LIST_NAME, getEmailNotificationToList()));

    props.setProperty(CLUSTER_SSL_ENABLED, toString(CLUSTER_SSL_ENABLED, isSSLEnabled()));
    props.setProperty(CLUSTER_SSL_PROTOCOLS, toString(CLUSTER_SSL_PROTOCOLS, getSSLProtocols()));
    props.setProperty(CLUSTER_SSL_CIPHERS, toString(CLUSTER_SSL_CIPHERS, getSSLCiphers()));
    props.setProperty(CLUSTER_SSL_REQUIRE_AUTHENTICATION,
        toString(CLUSTER_SSL_REQUIRE_AUTHENTICATION, isSSLAuthenticationRequired()));

    Properties sslProps = getSSLProperties();
    if (sslProps.size() > 0) {
      int sequence = 0;
      for (final Object o : sslProps.keySet()) {
        String key = (String) o;
        String value = sslProps.getProperty(key);
        props.setProperty("ssl-property-" + sequence, key + "=" + OBFUSCATED_STRING);
        sequence++;
      }
    }

    if (getDistributionLocatorConfigs().length > 0) {
      DistributionLocatorConfig[] configs = getDistributionLocatorConfigs();
      for (int i = 0; i < configs.length; i++) {
        DistributionLocatorConfig locator = configs[i];
        props.setProperty(LOCATOR_HOST_NAME + i, toString(LOCATOR_HOST_NAME, locator.getHost()));
        props.setProperty(LOCATOR_PORT_NAME + i, toString(LOCATOR_PORT_NAME, locator.getPort()));
        props.setProperty(LOCATOR_PRODUCT_DIRECTORY_NAME + i,
            toString(LOCATOR_PRODUCT_DIRECTORY_NAME, locator.getProductDirectory()));
        props.setProperty(LOCATOR_WORKING_DIRECTORY_NAME + i,
            toString(LOCATOR_WORKING_DIRECTORY_NAME, locator.getWorkingDirectory()));
        props.setProperty(LOCATOR_REMOTE_COMMAND + i,
            toString(LOCATOR_REMOTE_COMMAND, locator.getRemoteCommand()));
        props.setProperty(LOCATOR_BIND_ADDRESS + i,
            toString(LOCATOR_BIND_ADDRESS, locator.getBindAddress()));
      }
    }

    if (includeDSProperties) {
      props.setProperty(ENTITY_CONFIG_XML_FILE_NAME,
          toString(ENTITY_CONFIG_XML_FILE_NAME, getEntityConfigXMLFile()));
      props.setProperty(MCAST_PORT, toString(MCAST_PORT, getMcastPort()));
      props.setProperty(MCAST_ADDRESS, toString(MCAST_ADDRESS, getMcastAddress()));
      props.setProperty(LOCATORS, toString(LOCATORS, getLocators()));
      props.setProperty(MEMBERSHIP_PORT_RANGE_NAME, getMembershipPortRange());
      props.setProperty(TCP_PORT, "" + getTcpPort());
      props.setProperty(BIND_ADDRESS, toString(BIND_ADDRESS, getBindAddress()));
      props.setProperty(REMOTE_COMMAND_NAME, toString(REMOTE_COMMAND_NAME, getRemoteCommand()));
      props.setProperty(LOG_FILE_NAME, toString(LOG_FILE_NAME, getLogFile()));
      props.setProperty(LOG_LEVEL_NAME, toString(LOG_LEVEL_NAME, getLogLevel()));
      props.setProperty(LOG_DISK_SPACE_LIMIT_NAME,
          toString(LOG_DISK_SPACE_LIMIT_NAME, getLogDiskSpaceLimit()));
      props.setProperty(LOG_FILE_SIZE_LIMIT_NAME,
          toString(LOG_FILE_SIZE_LIMIT_NAME, getLogFileSizeLimit()));
      props.setProperty(REFRESH_INTERVAL_NAME,
          toString(REFRESH_INTERVAL_NAME, getRefreshInterval()));
    }

    return props;
  }


  // -------------------------------------------------------------------------
  // Agent specific properties
  // -------------------------------------------------------------------------

  @Override
  public boolean isAgentSSLEnabled() {
    return agentSSLEnabled;
  }

  @Override
  public void setAgentSSLEnabled(boolean agentSSLEnabled) {
    checkReadOnly();
    this.agentSSLEnabled = agentSSLEnabled;
    configChanged();
  }

  @Override
  public String getAgentSSLProtocols() {
    return agentSSLProtocols;
  }

  @Override
  public void setAgentSSLProtocols(String agentSSLProtocols) {
    checkReadOnly();
    this.agentSSLProtocols = agentSSLProtocols;
    configChanged();
  }

  @Override
  public String getAgentSSLCiphers() {
    return agentSSLCiphers;
  }

  @Override
  public void setAgentSSLCiphers(String agentSSLCiphers) {
    checkReadOnly();
    this.agentSSLCiphers = agentSSLCiphers;
    configChanged();
  }

  @Override
  public boolean isAgentSSLRequireAuth() {
    return agentSSLRequireAuth;
  }

  @Override
  public void setAgentSSLRequireAuth(boolean agentSSLRequireAuth) {
    checkReadOnly();
    this.agentSSLRequireAuth = agentSSLRequireAuth;
    configChanged();
  }

  @Override
  public boolean isHttpSSLRequireAuth() {
    return httpSSLRequireAuth;
  }

  @Override
  public void setHttpSSLRequireAuth(boolean httpSSLRequireAuth) {
    checkReadOnly();
    this.httpSSLRequireAuth = httpSSLRequireAuth;
    configChanged();
  }

  @Override
  public boolean isHttpAuthEnabled() {
    return httpAuthEnabled;
  }

  @Override
  public void setHttpAuthEnabled(boolean httpAuthEnabled) {
    checkReadOnly();
    this.httpAuthEnabled = httpAuthEnabled;
    configChanged();
  }

  @Override
  public String getHttpAuthUser() {
    return httpAuthUser;
  }

  @Override
  public void setHttpAuthUser(String httpAuthUser) {
    checkReadOnly();
    this.httpAuthUser = httpAuthUser;
    configChanged();
  }

  @Override
  public String getHttpAuthPassword() {
    return httpAuthPassword;
  }

  @Override
  public void setHttpAuthPassword(String httpAuthPassword) {
    checkReadOnly();
    this.httpAuthPassword = httpAuthPassword;
    configChanged();
  }

  @Override
  public boolean isSnmpEnabled() {
    return snmpEnabled;
  }

  @Override
  public void setSnmpEnabled(boolean snmpEnabled) {
    checkReadOnly();
    this.snmpEnabled = snmpEnabled;
    configChanged();
  }

  @Override
  public String getSnmpBindAddress() {
    return snmpBindAddress;
  }

  @Override
  public void setSnmpBindAddress(String snmpBindAddress) {
    checkReadOnly();
    this.snmpBindAddress = validateSnmpBindAddress(snmpBindAddress);
    configChanged();
  }

  @Override
  public String getSnmpDirectory() {
    return snmpDirectory;
  }

  @Override
  public void setSnmpDirectory(String snmpDirectory) {
    checkReadOnly();
    this.snmpDirectory = validateSnmpDirectory(snmpDirectory);
    configChanged();
  }

  @Override
  public boolean isRmiEnabled() {
    return rmiEnabled;
  }

  @Override
  public void setRmiEnabled(boolean rmiEnabled) {
    checkReadOnly();
    this.rmiEnabled = rmiEnabled;
    configChanged();
  }

  @Override
  public boolean isRmiRegistryEnabled() {
    return rmiRegistryEnabled;
  }

  @Override
  public void setRmiRegistryEnabled(boolean rmiRegistryEnabled) {
    checkReadOnly();
    this.rmiRegistryEnabled = rmiRegistryEnabled;
    configChanged();
  }

  @Override
  public String getRmiBindAddress() {
    return rmiBindAddress;
  }

  @Override
  public void setRmiBindAddress(String rmiBindAddress) {
    checkReadOnly();
    this.rmiBindAddress = validateRmiBindAddress(rmiBindAddress);
    configChanged();
  }

  @Override
  public int getRmiPort() {
    return rmiPort;
  }

  @Override
  public void setRmiPort(int rmiPort) {
    checkReadOnly();
    this.rmiPort = validateRmiPort(rmiPort);
    configChanged();
  }

  /**
   * Returns the port of the RMI Connector Server.
   * <p>
   * See <a href="#rmi-server-port">description</a> above.
   *
   * @return the value set for rmi-server-port
   *
   * @since GemFire 6.5
   */
  @Override
  public int getRmiServerPort() {
    return rmiServerPort;
  }

  /**
   * Sets the port of the RMI Connector Server.
   *
   * @param port rmi-server-port to set.
   *
   * @since GemFire 6.5
   */
  @Override
  public void setRmiServerPort(int port) {
    checkReadOnly();
    rmiServerPort = validateRmiServerPort(rmiServerPort);
    configChanged();
  }

  @Override
  public boolean isHttpEnabled() {
    return httpEnabled;
  }

  @Override
  public void setHttpEnabled(boolean httpEnabled) {
    checkReadOnly();
    this.httpEnabled = httpEnabled;
    configChanged();
  }

  @Override
  public int getHttpPort() {
    return httpPort;
  }

  @Override
  public void setHttpPort(int httpPort) {
    checkReadOnly();
    this.httpPort = validateHttpPort(httpPort);
    configChanged();
  }

  @Override
  public String getHttpBindAddress() {
    return httpBindAddress;
  }

  @Override
  public void setHttpBindAddress(String httpBindAddress) {
    checkReadOnly();
    this.httpBindAddress = validateHttpBindAddress(httpBindAddress);
    configChanged();
  }

  public void setHttpBindAddress(InetAddress httpBindAddress) {
    checkReadOnly();
    this.httpBindAddress = validateHttpBindAddress(httpBindAddress);
    configChanged();
  }

  @Override
  public boolean getAutoConnect() {
    return autoConnect;
  }

  @Override
  public void setAutoConnect(boolean v) {
    checkReadOnly();
    autoConnect = v;
    configChanged();
  }

  // -------------------------------------------------------------------------
  // Implementation methods
  // -------------------------------------------------------------------------

  /**
   * Initialize the values of this AgentConfig.
   *
   * @param props the configuration values to use
   */
  private void initialize(Properties props) {
    autoConnect = validateBoolean(props.getProperty(AUTO_CONNECT_NAME), DEFAULT_AUTO_CONNECT);

    httpEnabled = validateBoolean(props.getProperty(HTTP_ENABLED_NAME), DEFAULT_HTTP_ENABLED);
    httpBindAddress = validateHttpBindAddress(props.getProperty(HTTP_BIND_ADDRESS_NAME));
    httpPort = validateHttpPort(props.getProperty(HTTP_PORT_NAME));

    rmiEnabled = validateBoolean(props.getProperty(RMI_ENABLED_NAME), DEFAULT_RMI_ENABLED);
    rmiRegistryEnabled =
        validateBoolean(props.getProperty(RMI_REGISTRY_ENABLED_NAME), DEFAULT_RMI_REGISTRY_ENABLED);

    rmiBindAddress = validateRmiBindAddress(props.getProperty(RMI_BIND_ADDRESS_NAME));
    rmiPort = validateRmiPort(props.getProperty(RMI_PORT_NAME));
    rmiServerPort = validateRmiServerPort(props.getProperty(RMI_SERVER_PORT_NAME));

    snmpEnabled = validateBoolean(props.getProperty(SNMP_ENABLED_NAME), DEFAULT_SNMP_ENABLED);
    snmpDirectory = validateSnmpDirectory(props.getProperty(SNMP_DIRECTORY_NAME));

    agentSSLEnabled =
        validateBoolean(props.getProperty(AGENT_SSL_ENABLED_NAME), DEFAULT_AGENT_SSL_ENABLED);
    agentSSLProtocols = validateNonEmptyString(props.getProperty(AGENT_SSL_PROTOCOLS_NAME),
        DEFAULT_AGENT_SSL_PROTOCOLS);
    agentSSLCiphers = validateNonEmptyString(props.getProperty(AGENT_SSL_CIPHERS_NAME),
        DEFAULT_AGENT_SSL_CIPHERS);
    agentSSLRequireAuth =
        validateBoolean(props.getProperty(AGENT_SSL_REQUIRE_AUTHENTICATION_NAME),
            DEFAULT_AGENT_SSL_REQUIRE_AUTHENTICATION);
    httpSSLRequireAuth =
        validateBoolean(props.getProperty(HTTP_SSL_REQUIRE_AUTHENTICATION_NAME),
            DEFAULT_HTTP_SSL_REQUIRE_AUTHENTICATION);

    httpAuthEnabled = validateBoolean(props.getProperty(HTTP_AUTHENTICATION_ENABLED_NAME),
        DEFAULT_HTTP_AUTHENTICATION_ENABLED);
    httpAuthUser = validateNonEmptyString(props.getProperty(HTTP_AUTHENTICATION_USER_NAME),
        DEFAULT_HTTP_AUTHENTICATION_USER);
    httpAuthPassword = validateNonEmptyString(
        props.getProperty(HTTP_AUTHENTICATION_PASSWORD_NAME), DEFAULT_HTTP_AUTHENTICATION_PASSWORD);

    sslEnabled = validateBoolean(props.getProperty(CLUSTER_SSL_ENABLED), DEFAULT_SSL_ENABLED);
    sslProtocols =
        validateNonEmptyString(props.getProperty(CLUSTER_SSL_PROTOCOLS), DEFAULT_SSL_PROTOCOLS);
    sslCiphers =
        validateNonEmptyString(props.getProperty(CLUSTER_SSL_CIPHERS), DEFAULT_SSL_CIPHERS);
    sslAuthenticationRequired = validateBoolean(
        props.getProperty(CLUSTER_SSL_REQUIRE_AUTHENTICATION), DEFAULT_SSL_REQUIRE_AUTHENTICATION);
    sslProperties = new Properties();
    for (int i = 0; true; i++) {
      String key = "ssl-property-" + i;
      String value = props.getProperty(key);
      if (value == null) {
        break;
      }
      StringTokenizer st = new StringTokenizer(value, "=");
      if (!st.hasMoreTokens()) {
        break;
      }
      String propKey = st.nextToken();
      if (!st.hasMoreTokens()) {
        break;
      }
      String propValue = st.nextToken();
      sslProperties.put(propKey, propValue);
    }

    isEmailNotificationEnabled =
        validateBoolean(props.getProperty(AgentConfig.EMAIL_NOTIFICATIONS_ENABLED_NAME),
            DEFAULT_EMAIL_NOTIFICATIONS_ENABLED);
    emailNotificationHostName = validateNonEmptyString(
        props.getProperty(AgentConfig.EMAIL_NOTIFICATIONS_HOST_NAME), DEFAULT_EMAIL_HOST);
    emailNotificationFrom = validateNonEmptyString(
        props.getProperty(AgentConfig.EMAIL_NOTIFICATIONS_FROM_NAME), DEFAULT_EMAIL_FROM);
    emailNotificationToList = validateNonEmptyString(
        props.getProperty(AgentConfig.EMAIL_NOTIFICATIONS_TO_LIST_NAME), DEFAULT_EMAIL_TO_LIST);

    stateSaveFile = validateNonEmptyString(props.getProperty(AgentConfig.STATE_SAVE_FILE_NAME),
        DEFAULT_STATE_SAVE_FILE);

    try {
      for (int i = 0; true; i++) {
        String hostProp = props.getProperty(LOCATOR_HOST_NAME + i);
        if (isEmpty(hostProp)) {
          break;
        }
        String host = hostProp;
        int port = Integer.parseInt(props.getProperty(LOCATOR_PORT_NAME + i));
        File workDir =
            validateWorkingDirectory(props.getProperty(LOCATOR_WORKING_DIRECTORY_NAME + i));
        File prodDir = new File(
            validateProductDirectory(props.getProperty(LOCATOR_PRODUCT_DIRECTORY_NAME + i)));
        String remoteCmd = props.getProperty(LOCATOR_REMOTE_COMMAND + i);
        String bindAddr = props.getProperty(LOCATOR_BIND_ADDRESS + i);

        DistributionLocatorConfig config = createDistributionLocatorConfig();
        config.setHost(host);
        config.setPort(port);
        config.setBindAddress(bindAddr);
        config.setWorkingDirectory(workDir.getAbsolutePath());
        config.setProductDirectory(prodDir.getAbsolutePath());
        config.setRemoteCommand(remoteCmd);
      }
    } catch (IllegalArgumentException e) {
      // This is how we break out of the loop? Yuck!
      /*
       * LogWriter is initialized afterwards. Hence printing the stack trace. This is done to avoid
       * creation of duplicate log writer.
       */
      e.printStackTrace();
    }
  }

  /**
   * Filter all agent configuration attributes out of the given <code>Properties</code> object.
   * <p>
   *
   * @param props the <code>Properties</code> object of filter agent configuration attributes out
   *        of.
   *
   * @see AgentConfigImpl#_getPropertyDescription(String)
   */
  private static Properties filterOutAgentProperties(final Properties props) {
    final Properties filteredProps = new Properties();

    for (final Object key : props.keySet()) {
      if (_getPropertyDescription(key.toString()) == null) {
        final String value = props.getProperty(key.toString());
        if (value != null) {
          filteredProps.setProperty(key.toString(), value);
        }
      }
    }

    appendLogFileProperty(filteredProps);

    return filteredProps;
  }

  /**
   * Appends the log-file property to the Properties object if set of properties does not already
   * define the log-file property or the gemfire.agent.log-file property.
   * <p>
   *
   * @param props the <code>Properties</code> to append the log-file property to if the property
   *        does not exist.
   */
  private static void appendLogFileProperty(final Properties props) {
    if (!(props.containsKey(DistributedSystemConfig.LOG_FILE_NAME)
        || props.containsKey(SYSTEM_PROPERTY_PREFIX + DistributedSystemConfig.LOG_FILE_NAME))) {
      props.put(DistributedSystemConfig.LOG_FILE_NAME, DEFAULT_LOG_FILE);
    }
  }

  /**
   * Appends any additional property-file specified properties to the supplied Properties. If the
   * supplied property overrides the property in the property-file, then property-file value is
   * ignored. System Properties always override the supplied properties
   *
   * @return appendedProps Properties appened to from the property-file if any
   */
  private static Properties appendOptionalPropertyFileProperties(final Properties props) {
    final URL url = getPropertyFileURL(retrievePropertyFile());
    final Properties appendedProps = new Properties();

    appendedProps.putAll(props);

    // first, get any property values set in the optional property file
    if (url != null) {
      InputStream in = null;

      try {
        in = url.openStream();

        final Properties agentPropertyFileProperties = new Properties();

        agentPropertyFileProperties.load(in);

        // don't let any properties from the file override those on the command-line
        for (final Object key : agentPropertyFileProperties.keySet()) {
          if (props.getProperty(key.toString()) == null) {
            appendedProps.setProperty(key.toString(),
                agentPropertyFileProperties.getProperty(key.toString()));
          }
        }
      } catch (IOException e) {
        throw new GemFireIOException(
            String.format("Failed reading %s", url), e);
      } finally {
        IOUtils.close(in);
      }
    }

    // last override values with those from the system properties
    // TODO this is not exactly overriding!
    for (final Object propSuffix : props.keySet()) {
      final String key = SYSTEM_PROPERTY_PREFIX + propSuffix;
      final String value = System.getProperty(key);

      if (value != null) {
        appendedProps.put(key, value);
      }
    }

    return appendedProps;
  }

  /**
   * Returns a description of the given agent config property
   *
   * @throws IllegalArgumentException If <code>prop</code> is not a recognized agent configuration
   *         property
   */
  public static String getPropertyDescription(String prop) {
    if (prop.equals(LOG_FILE_NAME)) {
      return "Name of the agent's log file";
    } else if (prop.equals(LOG_LEVEL_NAME)) {
      return "Minimum level of logging performed by agent. Valid values are: all, finest, finer, fine, config, info, warning, error, severe and none.";
    } else if (prop.equals(AGENT_DEBUG)) {
      return "Whether the agent should print debugging information";
    } else if (prop.equals(LOG_DISK_SPACE_LIMIT_NAME)) {
      return "Limit, in megabytes, of how much disk space can be consumed by old inactive log files. This value (in megabytes) should be in the range: 0-1000000.";
    } else if (prop.equals(LOG_FILE_SIZE_LIMIT_NAME)) {
      return "Limit, in megabytes, of how large the current log file can grow before it is closed and log rolls on to a new file. This value (in megabytes) should be in the range: 0-1000000.";
    } else if (prop.equals(MCAST_PORT)) {
      return "Multicast port used to connect to distributed system. To use IP multicast, you must also define mcast-address. The value must be in the range: 0-65535.";
    } else if (prop.equals(MCAST_ADDRESS)) {
      return "Multicast address used to connect to distributed system. To use multicast, you must also define mcast-port, the IP port.";
    } else if (prop.equals(BIND_ADDRESS)) {
      return "An IP address of the JMX Agent's distributed system on a multi-homed host. On a multi-homed host, you must explicitly specify the bind address.";
    } else if (prop.equals(TCP_PORT)) {
      return "TCP/IP port number to use in the agent's distributed system";
    } else if (prop.equals(LOCATORS)) {
      return "A comma-separated list of address(es) of the locator(s) in the distributed system in host[port] form. E.g. locators=host1[port1],host2[port2],...,hostn[portn]";
    } else if (prop.equals(MEMBERSHIP_PORT_RANGE_NAME)) {
      return "The allowed range of UDP ports for use in forming an unique membership identifier. This range is given as two numbers separated by a minus sign.";
    } else if (prop.equals(ENTITY_CONFIG_XML_FILE_NAME)) {
      return "The name of an XML file that specifies the configuration for the managed entity administered by the Distributed System. The XML file must conform to the dtd - doc-files/ds5_0.dtd.";
    } else if (prop.equals(REFRESH_INTERVAL_NAME)) {
      return "Refresh Interval, in seconds, to be used for auto-refresh of SystemMember, StatisticResource and CacheServer refreshes";
    } else if (prop.equals(REMOTE_COMMAND_NAME)) {
      return "Command prefix used for launching members of the distributed system";
    } else if (prop.equals(CLUSTER_SSL_ENABLED)) {
      return "Whether to use the SSL protocol for communication between members of the admin distributed system. If set to true, locators should be used.";
    } else if (prop.equals(CLUSTER_SSL_PROTOCOLS)) {
      return "A space-separated list of the SSL protocols used to communicate with distributed system.";
    } else if (prop.equals(CLUSTER_SSL_CIPHERS)) {
      return "A space-separated list of the SSL ciphers to be used to communicate with distributed system.";
    } else if (prop.equals(CLUSTER_SSL_REQUIRE_AUTHENTICATION)) {
      return "Whether connection to the distributed system needs SSL authentication.";
    } else {
      String description = _getPropertyDescription(prop);
      if (description == null) {
        throw new IllegalArgumentException(
            String.format("Unknown config property: %s", prop));

      } else {
        return description;
      }
    }
  }

  /**
   * Returns a description of the given agent config property or <code>null</code> if
   * <code>prop</code> is not a recognized agent property.
   */
  public static String _getPropertyDescription(String prop) {
    if (prop.equals(AUTO_CONNECT_NAME)) {
      return "Whether the JMX agent will connect 'automatically' to the distributed system that it is configured to monitor.";
    } else if (prop.equals(HTTP_ENABLED_NAME)) {
      return "Whether the HTTP adapter is enabled in the JMX agent.";

    } else if (prop.equals(HTTP_BIND_ADDRESS_NAME)) {
      return "Bind address of HTTP adapter's sockets";

    } else if (prop.equals(HTTP_PORT_NAME)) {
      return "The port on which the HTTP adapter will be started. This value should be in the range: 0-65535.";

    } else if (prop.equals(RMI_ENABLED_NAME)) {
      return "Whether the RMI JMX adapter is enabled.";

    } else if (prop.equals(RMI_REGISTRY_ENABLED_NAME)) {
      return "Whether the JMX agent should start RMI registry. Alternatively, a registry outside of the JMX agent VM can be used.";

    } else if (prop.equals(RMI_BIND_ADDRESS_NAME)) {
      return "Bind address of RMI adapter's sockets";

    } else if (prop.equals(RMI_PORT_NAME)) {
      return "The port on which to contact the RMI registry. The value must be in the range: 0-65535.";

    } else if (prop.equals(RMI_SERVER_PORT_NAME)) {
      return "The port on which the RMI Connector Server should start. The value must be in the range: 0-65535.";

    } else if (prop.equals(SNMP_ENABLED_NAME)) {
      return "Whether the SNMP JMX adapter will be enabled.";

    } else if (prop.equals(SNMP_BIND_ADDRESS_NAME)) {
      return "Bind address of SNMP adapter's sockets";

    } else if (prop.equals(SNMP_DIRECTORY_NAME)) {
      return "The directory in which SNMP configuration resides";

    } else if (prop.equals(AGENT_SSL_ENABLED_NAME)) {
      return "Whether the JMX Agent will use the SSL protocol for communication.";

    } else if (prop.equals(AGENT_SSL_PROTOCOLS_NAME)) {
      return "The space-separated list of the SSL protocols to be used when connecting to the JMX agent.";

    } else if (prop.equals(AGENT_SSL_CIPHERS_NAME)) {
      return "The space-separated list of the SSL ciphers to be used when connecting to the JMX Agent.";

    } else if (prop.equals(AGENT_SSL_REQUIRE_AUTHENTICATION_NAME)) {
      return "Whether or not SSL connections to the RMI adapter require authentication. If true, needs client authentication for RMI and other non-HTTP connectors/adaptors.";

    } else if (prop.equals(HTTP_SSL_REQUIRE_AUTHENTICATION_NAME)) {
      return "Whether SSL connections to the HTTP adapter will need authentication.";

    } else if (prop.equals(HTTP_AUTHENTICATION_ENABLED_NAME)) {
      return "Whether the HTTP adapter will use HTTP authentication.";

    } else if (prop.equals(HTTP_AUTHENTICATION_USER_NAME)) {
      return "The user name for authentication in the HTTP JMX adapter";

    } else if (prop.equals(HTTP_AUTHENTICATION_PASSWORD_NAME)) {
      return "The password for authentication in the HTTP JMX adapter";

    } else if (prop.equals(PROPERTY_FILE_NAME)) {
      return "Name and path of the Agent's properties file from which agent reads configuration";

    } else if (prop.equals(LOCATOR_HOST_NAME)) {
      return "Host on which the distributed system's locator runs";

    } else if (prop.equals(LOCATOR_PORT_NAME)) {
      return "Host on which the distributed system's locator runs";

    } else if (prop.equals(LOCATOR_PRODUCT_DIRECTORY_NAME)) {
      return "GemFire product directory used to launch a locator";

    } else if (prop.equals(LOCATOR_WORKING_DIRECTORY_NAME)) {
      return "Directory in which a locator will be launched";

    } else if (prop.equals(LOCATOR_REMOTE_COMMAND)) {
      return "Command prefix used when launching a locator";

    } else if (prop.equals(LOCATOR_BIND_ADDRESS)) {
      return "IP address to use when contacting locator";

    } else if (prop.equals(LOCATOR_DS_PROPERTIES)) {
      return "Properties for configuring a locator's distributed system";

    } else if (prop.equals(EMAIL_NOTIFICATIONS_ENABLED_NAME)) {
      return "Whether the e-mail notifications are enabled.";

    } else if (prop.equals(EMAIL_NOTIFICATIONS_FROM_NAME)) {
      return "E-mail address to be used to send e-mail notifications.";

    } else if (prop.equals(EMAIL_NOTIFICATIONS_HOST_NAME)) {
      return "The host name of the e-mail server to be used for sending email notification.";

    } else if (prop.equals(EMAIL_NOTIFICATIONS_TO_LIST_NAME)) {
      return "A comma-separated list of recipient e-mail addresses to send e-mail notifications to.";

    } else if (prop.equals(STATE_SAVE_FILE_NAME)) {
      return "The name(not the path) of the file to be used for saving agent state. The file is stored in the same directory in which the agent.properties file is located.";

    } else {
      return null;
    }
  }

  /**
   * Parses the array of command-line arguments (format: key=value) into an instance of Properties.
   *
   * @param args the command-line arguments to convert into a Properties
   */
  private static Properties toProperties(String[] args) {
    Properties props = new Properties();
    // loop all args and pick out key=value pairs...
    for (final String arg : args) {
      // VM args...
      if (arg.startsWith("-J")) {
        int eq = arg.indexOf("=");
        String key = arg.substring(2, eq);
        String value = arg.substring(eq + 1);
        System.setProperty(key, value);
      } else if (arg.indexOf(AGENT_DEBUG) > 0) {
        int eq = arg.indexOf("=");
        String key = arg.substring(2, eq);
        String value = arg.substring(eq + 1);
        System.setProperty(key, value);
      }

      // all other args
      else if (arg.indexOf("=") > 0) {
        int eq = arg.indexOf("=");
        String key = arg.substring(0, eq);
        String value = arg.substring(eq + 1);
        props.setProperty(key, value);
      }
    }

    return props;
  }

  /**
   * Returns the original command-line arguments.
   */
  public String[] getOriginalArgs() {
    return originalCmdLineArgs;
  }

  // -------------------------------------------------------------------------
  // Validation methods for configuration options
  // -------------------------------------------------------------------------

  /**
   * Makes sure that the mcast port and locators are correct and consistent.
   *
   * @throws IllegalArgumentException If configuration is not valid
   */
  @Override
  public void validate() {
    super.validate();

    if (httpPort < 0 || httpPort > MAX_HTTP_PORT) {
      throw new IllegalArgumentException(
          String.format("%s must be zero or an integer between %s and %s.",
              HTTP_PORT_NAME, MIN_HTTP_PORT, MAX_HTTP_PORT));
    }

    if (rmiPort < 0 || rmiPort > MAX_RMI_PORT) {
      throw new IllegalArgumentException(
          String.format("%s must be zero or an integer between %s and %s.",
              RMI_PORT_NAME, MIN_RMI_PORT, MAX_RMI_PORT));
    }

    if (rmiServerPort < 0 || rmiServerPort > MAX_RMI_PORT) {
      throw new IllegalArgumentException(
          String.format("%s must be zero or an integer between %s and %s.",
              RMI_SERVER_PORT_NAME, MIN_RMI_PORT, MAX_RMI_PORT));
    }

  }

  /**
   * Returns defaultValue if value is empty.
   */
  private String validateNonEmptyString(String value, String defaultValue) {
    return isEmpty(value) ? defaultValue : value;
  }

  /**
   * Validates that systemHost can be used for an InetAddress.
   */
  private String validateSystemHost(String systemHost) {
    return validateHost(systemHost);
  }

  /**
   * Returns null if productDir is empty; else converts it to File.
   */
  private String validateProductDirectory(String productDir) {
    if (isEmpty(productDir)) {
      return null;
    }
    return productDir;
  }

  /**
   * Returns true if value parses as true; null value returns defaultValue.
   */
  private boolean validateBoolean(String value, boolean defaultValue) {
    if (isEmpty(value)) {
      return defaultValue;
    }
    return Boolean.parseBoolean(value);
  }

  // HttpAdaptor property validators...

  /**
   * Returns {@link org.apache.geode.admin.jmx.AgentConfig#DEFAULT_HTTP_PORT} if httpPort is empty;
   * else validates that it's an integer and returns the int form.
   */
  private int validateHttpPort(String val) {
    if (isEmpty(val)) {
      return DEFAULT_HTTP_PORT;
    } else {
      return validateHttpPort(Integer.parseInt(val));
    }
  }

  /**
   * Validates that httpPort is either zero or within the
   * {@link org.apache.geode.admin.jmx.AgentConfig#MIN_HTTP_PORT} and
   * {@link org.apache.geode.admin.jmx.AgentConfig#MAX_HTTP_PORT} values.
   */
  private int validateHttpPort(int val) {
    if (val < 0 || val > MAX_HTTP_PORT) {
      throw new IllegalArgumentException(
          String.format("%s must be zero or an integer between %s and %s.",
              HTTP_PORT_NAME, MIN_HTTP_PORT,
              MAX_HTTP_PORT));
    }
    return val;
  }

  /**
   * Returns {@link org.apache.geode.admin.jmx.AgentConfig#DEFAULT_HTTP_BIND_ADDRESS} unless
   * httpBindAddress can be used to create a valid InetAddress.
   */
  private String validateHttpBindAddress(String val) {
    String value = validateHost(val);
    if (value == null) {
      return DEFAULT_HTTP_BIND_ADDRESS;
    } else {
      return value;
    }
  }

  /**
   * Validates that httpBindAddress is not null and then returns the string form of it.
   */
  private String validateHttpBindAddress(InetAddress val) {
    if (val == null) {
      throw new IllegalArgumentException(
          "HttpBindAddress must not be null");
    }
    return toString("", val);
  }

  // SnmpAdaptor property validators...

  /**
   * Returns {@link org.apache.geode.admin.jmx.AgentConfig#DEFAULT_SNMP_BIND_ADDRESS} unless
   * snmpBindAddress can be used to create a valid InetAddress.
   */
  private String validateSnmpBindAddress(String val) {
    String value = validateHost(val);
    if (value == null) {
      return DEFAULT_SNMP_BIND_ADDRESS;
    } else {
      return value;
    }
  }

  /**
   * SnmpDirectory must be specified if SNMP is enabled. This directory must also exist.
   */
  private String validateSnmpDirectory(String snmpDir) {
    return snmpDir;
  }

  // RMIConnectorServer property validators...

  /**
   * Returns {@link org.apache.geode.admin.jmx.AgentConfig#DEFAULT_RMI_PORT} if rmiPort is empty;
   * else validates that it's an integer and returns the int form.
   */
  private int validateRmiPort(String val) {
    if (isEmpty(val)) {
      return DEFAULT_RMI_PORT;
    } else {
      return validateRmiPort(Integer.parseInt(val));
    }
  }

  /**
   * Validates that rmiPort is either zero or within the
   * {@link org.apache.geode.admin.jmx.AgentConfig#MIN_RMI_PORT} and
   * {@link org.apache.geode.admin.jmx.AgentConfig#MAX_RMI_PORT} values.
   */
  private int validateRmiPort(int val) {
    if (val < MIN_RMI_PORT || val > MAX_RMI_PORT) {
      throw new IllegalArgumentException(
          String.format("%s must be zero or an integer between %s and %s.",
              RMI_PORT_NAME, MIN_RMI_PORT, MAX_RMI_PORT));
    }
    return val;
  }

  /**
   * Returns {@link org.apache.geode.admin.jmx.AgentConfig#DEFAULT_RMI_SERVER_PORT} if
   * rmi-server-port is empty; else validates that it's an integer within the allowed range and
   * returns the int form.
   */
  private int validateRmiServerPort(String val) {
    if (isEmpty(val)) {
      return DEFAULT_RMI_SERVER_PORT;
    } else {
      return validateRmiServerPort(Integer.parseInt(val));
    }
  }

  /**
   * Validates that rmiPort is either zero or within the
   * {@link org.apache.geode.admin.jmx.AgentConfig#MIN_RMI_PORT} and
   * {@link org.apache.geode.admin.jmx.AgentConfig#MAX_RMI_PORT} values.
   */
  private int validateRmiServerPort(int val) {
    if (val < MIN_RMI_PORT || val > MAX_RMI_PORT) {
      throw new IllegalArgumentException(
          String.format("%s must be zero or an integer between %s and %s.",
              RMI_SERVER_PORT_NAME, MIN_RMI_PORT, MAX_RMI_PORT));
    }
    return val;
  }

  /**
   * Returns {@link org.apache.geode.admin.jmx.AgentConfig#DEFAULT_RMI_BIND_ADDRESS} unless
   * rmiBindAddress can be used to create a valid InetAddress.
   */
  private String validateRmiBindAddress(String val) {
    String value = validateHost(val);
    if (value == null) {
      return DEFAULT_RMI_BIND_ADDRESS;
    } else {
      return value;
    }
  }

  /**
   * Validates working directory is not null or empty.
   */
  private File validateWorkingDirectory(String workingDir) {
    if (isEmpty(workingDir)) {
      throw new IllegalArgumentException(
          "Locator WorkingDirectory must not be null");
    }
    return new File(workingDir);
  }

  // -------------------------------------------------------------------------
  // Static utility methods
  // -------------------------------------------------------------------------

  /**
   * Gets an <code>URL</code> for the property file, if one can be found, that the create method
   * would use to determine the systemName and product home.
   * <p>
   * The file will be searched for, in order, in the following locations:
   * <ol>
   * <li>the current directory
   * <li>the home directory
   * <li>the class path
   * </ol>
   * Only the first file found will be used.
   *
   * @return a <code>URL</code> that names the property file; otherwise Null if no property file was
   *         found.
   */
  public static URL getPropertyFileURL(final String propFileLocation) {
    File propFile = new File(propFileLocation);

    // first, try the current directory...
    if (propFile.exists()) {
      propFile = IOUtils.tryGetCanonicalFileElseGetAbsoluteFile(propFile);

      try {
        return propFile.toURI().toURL();
      } catch (java.net.MalformedURLException ignore) {
      }
    }

    // next, try the user's home directory...
    if (propFileLocation != null && propFileLocation.length() > 0) {
      propFile = new File(System.getProperty("user.home"), propFileLocation);

      if (propFile.exists()) {
        propFile = IOUtils.tryGetCanonicalFileElseGetAbsoluteFile(propFile);

        try {
          return propFile.toURI().toURL();
        } catch (java.net.MalformedURLException ignore) {
        }
      }
    }

    // finally, try the classpath...
    return ClassPathLoader.getLatest().getResource(AgentConfigImpl.class, propFileLocation);
  }

  private static boolean okToDisplayPropertyValue(String attName) {
    if (attName.startsWith(HTTP_AUTHENTICATION_USER_NAME)) {
      return false;
    }
    if (attName.startsWith(HTTP_AUTHENTICATION_PASSWORD_NAME)) {
      return false;
    }
    if (attName.startsWith(AGENT_SSL_PROTOCOLS_NAME)) {
      return false;
    }
    if (attName.startsWith(AGENT_SSL_CIPHERS_NAME)) {
      return false;
    }
    if (attName.toLowerCase().contains("javax.net.ssl")) {
      return false;
    }
    return !attName.toLowerCase().contains("password");
  }

  /**
   * Returns string representation of the specified object with special handling for InetAddress.
   *
   * @param obj the object to convert to string
   *
   * @return string representation of the specified object
   */
  private static String toString(String attName, java.lang.Object obj) {
    if (okToDisplayPropertyValue(attName)) {
      if (obj == null) {
        return "";
      }
      if (obj instanceof InetAddress) {
        return toHostString(obj);
      }
      return obj.toString();
    } else {
      return OBFUSCATED_STRING;
    }
  }

  /**
   * Returns string representation of the int.
   */
  private static String toString(String attName, int num) {
    if (okToDisplayPropertyValue(attName)) {
      return String.valueOf(num);
    } else {
      return OBFUSCATED_STRING;
    }
  }

  /**
   * Returns string representation of the boolean value.
   */
  private static String toString(String attName, boolean v) {
    if (okToDisplayPropertyValue(attName)) {
      return String.valueOf(v);
    } else {
      return OBFUSCATED_STRING;
    }
  }

  /**
   * Returns true if the string is null or empty.
   */
  public static boolean isEmpty(String string) {
    return string == null || string.length() == 0;
  }

  // -------------------------------------------------------------------------
  // SSL support...
  // -------------------------------------------------------------------------
  private boolean sslEnabled = DEFAULT_SSL_ENABLED;
  private String sslProtocols = DEFAULT_SSL_PROTOCOLS;
  private String sslCiphers = DEFAULT_SSL_CIPHERS;
  private boolean sslAuthenticationRequired = DEFAULT_SSL_REQUIRE_AUTHENTICATION;
  private Properties sslProperties = new Properties();

  @Override
  public boolean isSSLEnabled() {
    return sslEnabled;
  }

  @Override
  public void setSSLEnabled(boolean enabled) {
    sslEnabled = enabled;
    configChanged();
  }

  @Override
  public String getSSLProtocols() {
    return sslProtocols;
  }

  @Override
  public void setSSLProtocols(String protocols) {
    sslProtocols = protocols;
    configChanged();
  }

  @Override
  public String getSSLCiphers() {
    return sslCiphers;
  }

  @Override
  public void setSSLCiphers(String ciphers) {
    sslCiphers = ciphers;
    configChanged();
  }

  @Override
  public boolean isSSLAuthenticationRequired() {
    return sslAuthenticationRequired;
  }

  @Override
  public void setSSLAuthenticationRequired(boolean authRequired) {
    sslAuthenticationRequired = authRequired;
    configChanged();
  }

  @Override
  public Properties getSSLProperties() {
    return sslProperties;
  }

  @Override
  public void setSSLProperties(Properties sslProperties) {
    this.sslProperties = sslProperties;
    if (this.sslProperties == null) {
      this.sslProperties = new Properties();
    }
    configChanged();
  }

  @Override
  public String getStateSaveFile() {
    return stateSaveFile;
  }

  @Override
  public void setStateSaveFile(String file) {
    checkReadOnly();
    stateSaveFile = file;
    configChanged();
  }

  @Override
  public boolean isEmailNotificationEnabled() {
    return isEmailNotificationEnabled;
  }

  @Override
  public void setEmailNotificationEnabled(boolean enabled) {
    checkReadOnly();
    isEmailNotificationEnabled = enabled;
    configChanged();
  }

  @Override
  public String getEmailNotificationFrom() {
    return emailNotificationFrom;
  }

  @Override
  public void setEmailNotificationFrom(String emailID) {
    emailNotificationFrom = emailID;
    configChanged();
  }

  @Override
  public String getEmailNotificationHost() {
    return emailNotificationHostName;
  }

  @Override
  public void setEmailNotificationHost(String hostName) {
    emailNotificationHostName = hostName;
    configChanged();
  }

  @Override
  public String getEmailNotificationToList() {
    return emailNotificationToList;
  }

  @Override
  public void setEmailNotificationToList(String emailIDs) {
    emailNotificationToList = emailIDs;
    configChanged();
  }

  @Override
  public Object clone() throws CloneNotSupportedException {
    return super.clone();
  }
}
