org.chililog.server.common.AppProperties.java Source code

Java tutorial

Introduction

Here is the source code for org.chililog.server.common.AppProperties.java

Source

//
// Copyright 2010 Cinch Logic Pty Ltd.
//
// http://www.chililog.com
//
// Licensed 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.chililog.server.common;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Hashtable;
import java.util.Properties;
import java.util.UUID;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.WordUtils;

/**
 * <p>
 * AppProperties provides strongly typed access to configuration information in the <code>app.properties</code> file.
 * </p>
 * 
 * <p>
 * The <code>app.properties</code> file in the classpath contains the default configuration.
 * </p>
 * 
 * <p>
 * For example, if you created the file <code>/usr/local/chililog/config/app.properties</code>, then set the following
 * JVM option. <code>-Dchililog.config.directory=/usr/local/chililog/config</code>.
 * </p>
 * 
 * <h3>Example</h3>
 * 
 * <pre>
 * AppProperties.getInstance().getJsonPretty();
 * </pre>
 * 
 * <h3>Property Loading</h3>
 * 
 * We use convention to load the properties.
 * <ol>
 * <li>We search for all fields with upper case letters in their names. For example, <code>APP_NAME<code>.</li>
 * <li>We search for the corresponding field cache variable. The field name is converted to camel case and prefixed with
 * underscore. For example, <code>_appName</code></li>
 * <li>Next, we search for a load method to parse the entry in the property file. The field name is converted to camel
 * case and prefixed with "load". For example, <code>loadAppName</code></li>
 * <li>If the method is found, it is called and the result is used to set the cache variable identified in step #2.</li>
 * </ol>
 * 
 * 
 * @author vibul
 * @since 1.0
 */
public class AppProperties {

    private static Log4JLogger _logger = Log4JLogger.getLogger(AppProperties.class);
    private static final String APP_PROPERTY_FILE_NAME = "app.properties";

    /**
     * Returns the singleton instance for this class
     */
    public static AppProperties getInstance() {
        return SingletonHolder.INSTANCE;
    }

    /**
     * SingletonHolder is loaded on the first execution of Singleton.getInstance() or the first access to
     * SingletonHolder.INSTANCE, not before.
     * 
     * @see http://en.wikipedia.org/wiki/Singleton_pattern
     */
    private static class SingletonHolder {

        public static final AppProperties INSTANCE = new AppProperties();
    }

    /**
     * <p>
     * Singleton constructor that parses and loads the required application properties.
     * </p>
     * 
     * <p>
     * If there are any errors, the JVM is terminated. Without valid application properties, we will fall over elsewhere
     * so might as well terminate here.
     * </p>
     */
    private AppProperties() {
        try {
            loadProperties();
        } catch (Exception e) {
            _logger.error(e, "Error loading application properties: " + e.getMessage());
            System.exit(1);
        }
    }

    /**
     * <p>
     * Loads the configuration information from the <code>app.properties</code> file and caches then as strongly typed
     * values. This method is NOT thread-safe and should only be called for unit-testing.
     * </p>
     * 
     * <p>
     * <code>LoadProperties</code> first loads the default settings form the <code>app.properties</code> file the root
     * classpath and then any overrides from the <code>app.properties</code> file located in the in directory specified
     * in the "chililog.config.dir" system property.
     * </p>
     * 
     * @throws Exception
     */
    public void loadProperties() throws Exception {
        Properties properties = readPropertiesFile();
        parseProperties(properties);
    }

    /**
     * <p>
     * Loads the configuration information from the <code>app.properties</code> file.
     * </p>
     * 
     * <p>
     * <code>LoadProperties</code> first loads the default settings form the <code>app.properties</code> file the root
     * classpath and then any overrides from the <code>app.properties</code> file located in the in directory specified
     * in the "chililog.config.dir" system property.
     * </p>
     * 
     * @throws IOException
     * @throws FileNotFoundException
     */
    static Properties readPropertiesFile() throws FileNotFoundException, IOException {
        FileInputStream fis = null;

        try {
            Properties properties = new Properties();

            // Load default from class path
            InputStream is = AppProperties.class.getClassLoader().getResourceAsStream(APP_PROPERTY_FILE_NAME);
            if (is == null) {
                throw new FileNotFoundException("'app.properties' file not found in classpath");
            }
            properties.load(is);
            is.close();

            return properties;
        } finally {
            if (fis != null) {
                fis.close();
            }
        }
    }

    /**
     * <p>
     * Parses the properties into strongly typed class fields.
     * </p>
     * 
     * <p>
     * Use reflection to simulate the likes of: <code>_appName = loadAppName(properties);</code>
     * </p>
     * 
     * @param properties
     *            Properties to parse
     * @throws Exception
     */
    private void parseProperties(Properties properties) throws Exception {
        Class<AppProperties> cls = AppProperties.class;
        Field[] ff = cls.getDeclaredFields();
        for (Field f : ff) {
            // Look for field names like APP_NAME
            String propertyNameFieldName = f.getName();
            if (!propertyNameFieldName.matches("^[A-Z0-9_]+$")) {
                continue;
            }

            // Build cache field (_appName) and method (loadAppName) methods
            String baseName = WordUtils.capitalizeFully(propertyNameFieldName, new char[] { '_' });
            baseName = baseName.replace("_", "");
            String cacheMethodName = "load" + baseName;
            String cacheFieldName = "_" + StringUtils.uncapitalize(baseName);

            // If field not exist, then skip
            Field cacheField = null;
            try {
                cacheField = cls.getDeclaredField(cacheFieldName);
            } catch (NoSuchFieldException e) {
                continue;
            }

            // Get and set the value
            Method m = cls.getDeclaredMethod(cacheMethodName, Properties.class);
            Object cacheValue = m.invoke(null, properties);
            cacheField.set(this, cacheValue);
        }

        return;
    }

    // *****************************************************************************************************************
    // *****************************************************************************************************************
    // Miscellaneous
    // *****************************************************************************************************************
    // *****************************************************************************************************************

    /**
     * If true, JSON serialization is to be human readable. If false, white spaces will be eliminated.
     */
    public boolean getJsonPretty() {
        return _jsonPretty;
    }

    static final String JSON_PRETTY = "json.pretty";

    private boolean _jsonPretty = false;

    static boolean loadJsonPretty(Properties properties) {
        return loadBoolean(properties, JSON_PRETTY, false);
    }

    // *****************************************************************************************************************
    // *****************************************************************************************************************
    // Database
    // *****************************************************************************************************************
    // *****************************************************************************************************************

    /**
     * Returns the IP address of the mongoDB Database Server
     */
    public String getDbIpAddress() {
        return _dbIpAddress;
    }

    static final String DB_IP_ADDRESS = "db.ip_address";

    private String _dbIpAddress = null;

    static String loadDbIpAddress(Properties properties) {
        return loadString(properties, DB_IP_ADDRESS);
    }

    /**
     * Returns the IP port that the mongoDB Database Server is listening on. Defaults to 27017 if not set.
     */
    public int getDbIpPort() {
        return _dbIpPort;
    }

    static final String DB_IP_PORT = "db.ip_port";

    private int _dbIpPort = 0;

    static int loadDbIpPort(Properties properties) {
        return loadInt(properties, DB_IP_PORT, 27017);
    }

    /**
     * Returns the name of the database within the mongoDB server to use
     */
    public String getDbName() {
        return _dbName;
    }

    static final String DB_NAME = "db.name";

    private String _dbName = null;

    static String loadDbName(Properties properties) {
        return loadString(properties, DB_NAME);
    }

    /**
     * Returns the usename to use for authenticating of the mongoDB database
     */
    public String getDbUserName() {
        return _dbUserName;
    }

    static final String DB_USER_NAME = "db.username";

    private String _dbUserName = null;

    static String loadDbUserName(Properties properties) {
        return loadString(properties, DB_USER_NAME);
    }

    /**
     * Returns the password to use for authenticating of the mongoDB database
     */
    public String getDbPassword() {
        return _dbPassword;
    }

    static final String DB_PASSWORD = "db.password";

    private String _dbPassword = null;

    static String loadDbPassword(Properties properties) {
        return loadString(properties, DB_PASSWORD);
    }

    /**
     * Returns the number of connections per host. The default is 10.
     */
    public int getDbConnectionsPerHost() {
        return _dbConnectionsPerHost;
    }

    static final String DB_CONNECTIONS_PER_HOST = "db.connections_per_host";

    private int _dbConnectionsPerHost = 0;

    static int loadDbConnectionsPerHost(Properties properties) {
        return loadInt(properties, DB_CONNECTIONS_PER_HOST, 10);
    }

    // *****************************************************************************************************************
    // *****************************************************************************************************************
    // Message Queue
    // *****************************************************************************************************************
    // *****************************************************************************************************************

    /**
     * Returns The name of the ChiliLog system user. This auto-create user will have permission to manage all aspects of
     * ChiliLog. If it is not set, then we generate a random one. It should be set for load-balanced installations.
     */
    public String getMqSystemUsername() {
        return _mqSystemUsername;
    }

    static final String MQ_SYSTEM_USERNAME = "mq.system_username";

    private String _mqSystemUsername = null;

    static String loadMqSystemUsername(Properties properties) {
        String s = loadString(properties, MQ_SYSTEM_USERNAME, StringUtils.EMPTY);
        if (StringUtils.isBlank(s)) {
            s = "systemuser_" + UUID.randomUUID().toString();
        }
        return s;
    }

    /**
     * Returns The password of the ChiliLog system user. This auto-create user will have permission to manage all
     * aspects of ChiliLog. If it is not set, then we generate a random one.
     */
    public String getMqSystemPassword() {
        return _mqSystemPassword;
    }

    static final String MQ_SYSTEM_PASSWORD = "mq.system_password";

    private String _mqSystemPassword = null;

    static String loadMqSystemPassword(Properties properties) {
        String s = loadString(properties, MQ_SYSTEM_PASSWORD, StringUtils.EMPTY);
        if (StringUtils.isBlank(s)) {
            s = UUID.randomUUID().toString();
        }
        return s;
    }

    /**
     * Returns Flag to indicate if journalling is enabled or not. If so, then messages from message queues flagged as
     * durable will be persisted (or journalled). Default is false.
     */
    public boolean getMqJournallingEnabled() {
        return _mqJournallingEnabled;
    }

    static final String MQ_JOURNALLING_ENABLED = "mq.journalling_enabled";

    private boolean _mqJournallingEnabled = false;

    static boolean loadMqJournallingEnabled(Properties properties) {
        return loadBoolean(properties, MQ_JOURNALLING_ENABLED, false);
    }

    /**
     * Returns the directory to store journal files
     */
    public String getMqJournalDirectory() {
        return _mqJournalDirectory;
    }

    static final String MQ_JOURNAL_DIRECTORY = "mq.journal_directory";

    private String _mqJournalDirectory = null;

    static String loadMqJournalDirectory(Properties properties) {
        String s = loadString(properties, MQ_JOURNAL_DIRECTORY);
        return s;
    }

    /**
     * Returns the directory to store paging files.
     */
    public String getMqPagingDirectory() {
        return _mqPagingDirectory;
    }

    static final String MQ_PAGING_DIRECTORY = "mq.paging_directory";

    private String _mqPagingDirectory = null;

    static String loadMqPagingDirectory(Properties properties) {
        String s = loadString(properties, MQ_PAGING_DIRECTORY);
        return s;
    }

    /**
     * Returns the time period in milliseconds during which an authenticated user is valid and credentials will not be
     * validated by calling JAAS. Defaults to 10000 (10 seconds).
     */
    public int getMqSecurityInvalidationInterval() {
        return _mqSecurityInvalidationInterval;
    }

    static final String MQ_SECURITY_INVALIDATION_INTERVAL = "mq.security_invalidation_interval";

    private int _mqSecurityInvalidationInterval = 10000;

    static int loadMqSecurityInvalidationInterval(Properties properties) {
        int i = loadInt(properties, MQ_SECURITY_INVALIDATION_INTERVAL, 10000);
        return i;
    }

    /**
     * Returns Flag to indicate if message queue clustering is to be used. Default is false.
     */
    public boolean getMqClusteredEnabled() {
        return _mqClusteredEnabled;
    }

    static final String MQ_CLUSTERED_ENABLED = "mq.clustered_enabled";

    private boolean _mqClusteredEnabled = false;

    static boolean loadMqClusteredEnabled(Properties properties) {
        return loadBoolean(properties, MQ_CLUSTERED_ENABLED, false);
    }

    /**
     * Returns the maximum number of delivery attempts that will be made before a message is deleted or placed on the
     * dead letter queue. A message is catergorised as failed if it has been acknowledge AND its transactional session
     * is rolled back.
     */
    public int getMqRedeliveryMaxAttempts() {
        return _mqRedeliveryMaxAttempts;
    }

    static final String MQ_REDELIVERY_MAX_ATTEMPTS = "mq.redelivery.max_attempts";

    private int _mqRedeliveryMaxAttempts = -1;

    static int loadMqRedeliveryMaxAttempts(Properties properties) {
        return loadInt(properties, MQ_REDELIVERY_MAX_ATTEMPTS);
    }

    /**
     * Returns the number of milliseconds before a re-delivery of a failed message is made.
     */
    public int getMqRedeliveryDelayMilliseconds() {
        return _mqRedeliveryDelayMilliseconds;
    }

    static final String MQ_REDELIVERY_DELAY_MILLISECONDS = "mq.redelivery.delay_milliseconds";

    private int _mqRedeliveryDelayMilliseconds = -1;

    static int loadMqRedeliveryDelayMilliseconds(Properties properties) {
        return loadInt(properties, MQ_REDELIVERY_DELAY_MILLISECONDS);
    }

    /**
     * Returns the address to send for undelivered messages
     */
    public String getMqDeadLetterAddress() {
        return _mqDeadLetterAddress;
    }

    static final String MQ_DEAD_LETTER_ADDRESS = "mq.dead_letter_address";

    private String _mqDeadLetterAddress = null;

    static String loadMqDeadLetterAddress(Properties properties) {
        return loadString(properties, MQ_DEAD_LETTER_ADDRESS, null);
    }

    // *****************************************************************************************************************
    // *****************************************************************************************************************
    // PUB SUB
    // *****************************************************************************************************************
    // *****************************************************************************************************************

    /**
     * Returns Flag to indicate if the message queue HornetQ and JMS protocols are to be enabled for pubsub use
     */
    public boolean getPubSubCoreProtocolEnabled() {
        return _pubSubCoreProtocolEnabled;
    }

    static final String PUB_SUB_CORE_PROTOCOL_ENABLED = "pubsub.core.enabled";

    private boolean _pubSubCoreProtocolEnabled = false;

    static boolean loadPubSubCoreProtocolEnabled(Properties properties) {
        return loadBoolean(properties, PUB_SUB_CORE_PROTOCOL_ENABLED, false);
    }

    /**
     * Returns configuration settings for the message queue HornetQ and JMS protocols
     */
    public Hashtable<String, Object> getPubSubCoreProtocolConfig() {
        return _pubSubCoreProtocolConfig;
    }

    static final String PUB_SUB_CORE_PROTOCOL_CONFIG = "pubsub.core.";

    private Hashtable<String, Object> _pubSubCoreProtocolConfig = null;

    static Hashtable<String, Object> loadPubSubCoreProtocolConfig(Properties properties) {
        Hashtable<String, Object> m = new Hashtable<String, Object>();
        for (Object key : properties.keySet()) {
            String keyAsString = (String) key;
            if (keyAsString.startsWith(PUB_SUB_CORE_PROTOCOL_CONFIG)
                    && !keyAsString.equalsIgnoreCase("pubsub.core.enabled")) {
                String value = properties.getProperty(keyAsString);
                if (!StringUtils.isBlank(value)) {
                    m.put(keyAsString.substring(PUB_SUB_CORE_PROTOCOL_CONFIG.length()), value);
                }
            }
        }
        return m;
    }

    /**
     * Returns Flag to indicate if the JSON HTTP protocol is to be enabled for pubsub use
     */
    public boolean getPubSubJsonHttpEnabled() {
        return _pubSubJsonHttpEnabled;
    }

    static final String PUB_SUB_JSON_HTTP_ENABLED = "pubsub.json-http.enabled";

    private boolean _pubSubJsonHttpEnabled = false;

    static boolean loadPubSubJsonHttpEnabled(Properties properties) {
        return loadBoolean(properties, PUB_SUB_JSON_HTTP_ENABLED, false);
    }

    /**
     * Returns the IP address to use for binding our UI web server
     */
    public String getPubSubJsonHttpHost() {
        return _pubSubJsonHttpHost;
    }

    static final String PUB_SUB_JSON_HTTP_HOST = "pubsub.json-http.host";

    private String _pubSubJsonHttpHost = null;

    static String loadPubSubJsonHttpHost(Properties properties) {
        return loadString(properties, PUB_SUB_JSON_HTTP_HOST);
    }

    /**
     * Returns the IP port to use for binding our UI web server
     */
    public int getPubSubJsonHttpPort() {
        return _pubSubJsonHttpPort;
    }

    static final String PUB_SUB_JSON_HTTP_PORT = "pubsub.json-http.port";

    private int _pubSubJsonHttpPort = 0;

    static int loadPubSubJsonHttpPort(Properties properties) {
        return loadInt(properties, PUB_SUB_JSON_HTTP_PORT, 61615);
    }

    /**
     * Returns the maximum number of active threads used by netty to execute our handler
     */
    public int getPubSubJsonHttpNettyWorkerThreadPoolSize() {
        return _pubSubJsonHttpNettyWorkerThreadPoolSize;
    }

    static final String PUB_SUB_JSON_HTTP_NETTY_WORKER_THREAD_POOL_SIZE = "pubsub.json-http.netty_worker_thread_pool.size";

    private int _pubSubJsonHttpNettyWorkerThreadPoolSize = 0;

    static int loadPubSubJsonHttpNettyWorkerThreadPoolSize(Properties properties) {
        return loadInt(properties, PUB_SUB_JSON_HTTP_NETTY_WORKER_THREAD_POOL_SIZE, 0);
    }

    /**
     * Returns the maximum number of active threads used by netty to execute our handler
     */
    public int getPubSubJsonHttpNettyHandlerThreadPoolSize() {
        return _pubSubJsonHttpNettyHandlerThreadPoolSize;
    }

    static final String PUB_SUB_JSON_HTTP_NETTY_HANDLER_THREAD_POOL_SIZE = "pubsub.json-http.netty_handler_thread_pool.size";

    private int _pubSubJsonHttpNettyHandlerThreadPoolSize = 0;

    static int loadPubSubJsonHttpNettyHandlerThreadPoolSize(Properties properties) {
        return loadInt(properties, PUB_SUB_JSON_HTTP_NETTY_HANDLER_THREAD_POOL_SIZE, 16);
    }

    /**
     * Returns Flag to indicate if the SSL is to be supported
     */
    public boolean getPubSubJsonHttpSslEnabled() {
        return _pubSubJsonHttpSslEnabled;
    }

    static final String PUB_SUB_JSON_HTTP_SSL_ENABLED = "pubsub.json-http.ssl_enabled";

    private boolean _pubSubJsonHttpSslEnabled = false;

    static boolean loadPubSubJsonHttpSslEnabled(Properties properties) {
        return loadBoolean(properties, PUB_SUB_JSON_HTTP_SSL_ENABLED, false);
    }

    /**
     * Returns the path to the key store to use for SSL
     */
    public String getPubSubJsonHttpKeyStorePath() {
        return _pubSubJsonHttpKeyStorePath;
    }

    static final String PUB_SUB_JSON_HTTP_KEY_STORE_PATH = "pubsub.json-http.key_store_path";

    private String _pubSubJsonHttpKeyStorePath = null;

    static String loadPubSubJsonHttpKeyStorePath(Properties properties) {
        return loadString(properties, PUB_SUB_JSON_HTTP_KEY_STORE_PATH, null);
    }

    /**
     * Returns the password to the key store to use for SSL
     */
    public String getPubSubJsonHttpKeyStorePassword() {
        return _pubSubJsonHttpKeyStorePassword;
    }

    static final String PUB_SUB_JSON_HTTP_KEY_STORE_PASSWORD = "pubsub.json-http.key_store_password";

    private String _pubSubJsonHttpKeyStorePassword = null;

    static String loadPubSubJsonHttpKeyStorePassword(Properties properties) {
        return loadString(properties, PUB_SUB_JSON_HTTP_KEY_STORE_PASSWORD, null);
    }

    /**
     * Returns the password to the key inside to the key store to use for SSL
     */
    public String getPubSubJsonHttpKeyStoreKeyPassword() {
        return _pubSubJsonHttpKeyStoreKeyPassword;
    }

    static final String PUB_SUB_JSON_HTTP_KEY_STORE_KEY_PASSWORD = "pubsub.json-http.key_store_key_password";

    private String _pubSubJsonHttpKeyStoreKeyPassword = null;

    static String loadPubSubJsonHttpKeyStoreKeyPassword(Properties properties) {
        return loadString(properties, PUB_SUB_JSON_HTTP_KEY_STORE_KEY_PASSWORD, null);
    }

    /**
     * Returns the path to the trust store to use for SSL
     */
    public String getPubSubJsonHttpTrustStorePath() {
        return _pubSubJsonHttpTrustStorePath;
    }

    static final String PUB_SUB_JSON_HTTP_TRUST_STORE_PATH = "pubsub.json-http.trust_store_path";

    private String _pubSubJsonHttpTrustStorePath = null;

    static String loadPubSubJsonHttpTrustStorePath(Properties properties) {
        return loadString(properties, PUB_SUB_JSON_HTTP_TRUST_STORE_PATH, null);
    }

    /**
     * Returns the password to the trust store to use for SSL
     */
    public String getPubSubJsonHttpTrustStorePassword() {
        return _pubSubJsonHttpTrustStorePassword;
    }

    static final String PUB_SUB_JSON_HTTP_TRUST_STORE_PASSWORD = "pubsub.json-http.trust_store_password";

    private String _pubSubJsonHttpTrustStorePassword = null;

    static String loadPubSubJsonHttpTrustStorePassword(Properties properties) {
        return loadString(properties, PUB_SUB_JSON_HTTP_TRUST_STORE_PASSWORD, null);
    }

    // *****************************************************************************************************************
    // *****************************************************************************************************************
    // WORKBENCH
    // *****************************************************************************************************************
    // *****************************************************************************************************************
    /**
     * Returns Flag to indicate if the workbench service is to be enabled
     */
    public boolean getWorkbenchEnabled() {
        return _workbenchEnabled;
    }

    static final String WORKBENCH_ENABLED = "workbench.enabled";

    private boolean _workbenchEnabled = true;

    static boolean loadWorkbenchEnabled(Properties properties) {
        return loadBoolean(properties, WORKBENCH_ENABLED, true);
    }

    /**
     * <p>
     * Returns the IP address to use for binding our WorkBench web server
     * </p>
     * <p>
     * Can specify <tt>0.0.0.0</tt> which means any address. Can also specify comma separated hostnames/ip address. e.g.
     * <tt>localhost,192.168.1.1</tt>
     * </p>
     */
    public String getWorkbenchHost() {
        return _workbenchHost;
    }

    static final String WORKBENCH_HOST = "workbench.host";

    private String _workbenchHost = null;

    static String loadWorkbenchHost(Properties properties) {
        return loadString(properties, WORKBENCH_HOST);
    }

    /**
     * Returns the IP port to use for binding our WorkBench web server
     */
    public int getWorkbenchPort() {
        return _workbenchPort;
    }

    static final String WORKBENCH_PORT = "workbench.port";

    private int _workbenchPort = 0;

    static int loadWorkbenchPort(Properties properties) {
        return loadInt(properties, WORKBENCH_PORT, 8989);
    }

    /**
     * Returns the maximum number of threads in the thread pool used by netty for processing channels 
     */
    public int getWorkbenchNettyWorkerThreadPoolSize() {
        return _workbenchNettyWorkerThreadPoolSize;
    }

    static final String WORKBENCH_NETTY_WORKER_THREAD_POOL_SIZE = "workbench.netty_worker_thread_pool.size";

    private int _workbenchNettyWorkerThreadPoolSize = 0;

    static int loadWorkbenchNettyWorkerThreadPoolSize(Properties properties) {
        return loadInt(properties, WORKBENCH_NETTY_WORKER_THREAD_POOL_SIZE, 0);
    }

    /**
     * Returns the maximum number of threads in the thread pool used by netty to execute our handler
     */
    public int getWorkbenchNettyHandlerThreadPoolSize() {
        return _workbenchNettyHandlerThreadPoolSize;
    }

    static final String WORKBENCH_NETTY_HANDLER_THREAD_POOL_SIZE = "workbench.netty_handler_thread_pool.size";

    private int _workbenchNettyHandlerThreadPoolSize = 0;

    static int loadWorkbenchNettyHandlerThreadPoolSize(Properties properties) {
        return loadInt(properties, WORKBENCH_NETTY_HANDLER_THREAD_POOL_SIZE, 16);
    }

    /**
     * Returns maximum total size of the queued events per channel. 0 to disable. Defaults to 0.
     */
    public long getWorkbenchNettyHandlerThreadPoolMaxChannelMemorySize() {
        return _workbenchNettyHandlerThreadPoolMaxChannelMemorySize;
    }

    static final String WORKBENCH_NETTY_HANDLER_THREAD_POOL_MAX_CHANNEL_MEMORY_SIZE = "workbench.netty_handler_thread_pool.max_channel_memory_size";

    private long _workbenchNettyHandlerThreadPoolMaxChannelMemorySize = 0;

    static long loadWorkbenchNettyHandlerThreadPoolMaxChannelMemorySize(Properties properties) {
        return loadLong(properties, WORKBENCH_NETTY_HANDLER_THREAD_POOL_MAX_CHANNEL_MEMORY_SIZE, 0);
    }

    /**
     * Returns maximum total size of the queued events for this pool. 0 to disable. Defaults to 0
     */
    public long getWorkbenchNettyHandlerThreadPoolMaxTotalMemorySize() {
        return _workbenchNettyHandlerThreadPoolMaxTotalMemorySize;
    }

    static final String WORKBENCH_NETTY_HANDLER_THREAD_POOL_MAX_TOTAL_MEMORY_SIZE = "workbench.netty_handler_thread_pool.max_total_memory_size";

    private long _workbenchNettyHandlerThreadPoolMaxTotalMemorySize = 0;

    static long loadWorkbenchNettyHandlerThreadPoolMaxTotalMemorySize(Properties properties) {
        return loadLong(properties, WORKBENCH_NETTY_HANDLER_THREAD_POOL_MAX_TOTAL_MEMORY_SIZE, 0);
    }

    /**
     * Returns the amount of time for an inactive thread before it is terminated. Defaults to 3 seconds.
     */
    public int getWorkbenchNettyHandlerThreadPoolKeepAliveSeconds() {
        return _workbenchNettyHandlerThreadPoolKeepAliveSeconds;
    }

    static final String WORKBENCH_NETTY_HANDLER_THREAD_POOL_KEEP_ALIVE_SECONDS = "workbench.netty_handler_thread_pool.keep_alive_seconds";

    private int _workbenchNettyHandlerThreadPoolKeepAliveSeconds = 0;

    static int loadWorkbenchNettyHandlerThreadPoolKeepAliveSeconds(Properties properties) {
        return loadInt(properties, WORKBENCH_NETTY_HANDLER_THREAD_POOL_KEEP_ALIVE_SECONDS, 3);
    }

    /**
     * Returns Flag to indicate if the SSL is to be supported
     */
    public boolean getWorkbenchSslEnabled() {
        return _workbenchSslEnabled;
    }

    static final String WORKBENCH_SSL_ENABLED = "workbench.ssl_enabled";

    private boolean _workbenchSslEnabled = false;

    static boolean loadWorkbenchSslEnabled(Properties properties) {
        return loadBoolean(properties, WORKBENCH_SSL_ENABLED, false);
    }

    /**
     * Returns the path to the key store to use for SSL
     */
    public String getWorkbenchKeyStorePath() {
        return _workbenchKeyStorePath;
    }

    static final String WORKBENCH_KEY_STORE_PATH = "workbench.key_store_path";

    private String _workbenchKeyStorePath = null;

    static String loadWorkbenchKeyStorePath(Properties properties) {
        return loadString(properties, WORKBENCH_KEY_STORE_PATH, null);
    }

    /**
     * Returns the password to the key store to use for SSL
     */
    public String getWorkbenchKeyStorePassword() {
        return _workbenchKeyStorePassword;
    }

    static final String WORKBENCH_KEY_STORE_PASSWORD = "workbench.key_store_password";

    private String _workbenchKeyStorePassword = null;

    static String loadWorkbenchKeyStorePassword(Properties properties) {
        return loadString(properties, WORKBENCH_KEY_STORE_PASSWORD, null);
    }

    /**
     * Returns the password to the key inside to the key store to use for SSL
     */
    public String getWorkbenchKeyStoreKeyPassword() {
        return _workbenchKeyStoreKeyPassword;
    }

    static final String WORKBENCH_KEY_STORE_KEY_PASSWORD = "workbench.key_store_key_password";

    private String _workbenchKeyStoreKeyPassword = null;

    static String loadWorkbenchKeyStoreKeyPassword(Properties properties) {
        return loadString(properties, WORKBENCH_KEY_STORE_KEY_PASSWORD, null);
    }

    /**
     * Returns the path to the trust store to use for SSL
     */
    public String getWorkbenchTrustStorePath() {
        return _workbenchTrustStorePath;
    }

    static final String WORKBENCH_TRUST_STORE_PATH = "workbench.trust_store_path";

    private String _workbenchTrustStorePath = null;

    static String loadWorkbenchTrustStorePath(Properties properties) {
        return loadString(properties, WORKBENCH_TRUST_STORE_PATH, null);
    }

    /**
     * Returns the password to the trust store to use for SSL
     */
    public String getWorkbenchTrustStorePassword() {
        return _workbenchTrustStorePassword;
    }

    static final String WORKBENCH_TRUST_STORE_PASSWORD = "workbench.trust_store_password";

    private String _workbenchTrustStorePassword = null;

    static String loadWorkbenchTrustStorePassword(Properties properties) {
        return loadString(properties, WORKBENCH_TRUST_STORE_PASSWORD, null);
    }

    /**
     * Returns the password to the trust store to use for SSL
     */
    public String getWorkbenchStaticFilesDirectory() {
        return _workbenchStaticFilesDirectory;
    }

    static final String WORKBENCH_STATIC_FILES_DIRECTORY = "workbench.static_files.directory";

    private String _workbenchStaticFilesDirectory = null;

    static String loadWorkbenchStaticFilesDirectory(Properties properties) {
        return loadString(properties, WORKBENCH_STATIC_FILES_DIRECTORY, ".");
    }

    /**
     * Returns the number of seconds static files are cached in the browser
     */
    public int getWorkbenchStaticFilesCacheSeconds() {
        return _workbenchStaticFilesCacheSeconds;
    }

    static final String WORKBENCH_STATIC_FILES_CACHE_SECONDS = "workbench.static_files.cache_seconds";

    private int _workbenchStaticFilesCacheSeconds = 0;

    static int loadWorkbenchStaticFilesCacheSeconds(Properties properties) {
        return loadInt(properties, WORKBENCH_STATIC_FILES_CACHE_SECONDS, 0);
    }

    /**
     * Returns the salt to use for hashing of the authentication token
     */
    public byte[] getWorkbenchApiAuthenticationHashSalt() {
        return _workbenchApiAuthenticationHashSalt;
    }

    static final String WORKBENCH_API_AUTHENTICATION_HASH_SALT = "workbench.api.authentication.hash_salt";

    private byte[] _workbenchApiAuthenticationHashSalt = null;

    static byte[] loadWorkbenchApiAuthenticationHashSalt(Properties properties) {
        try {
            return loadString(properties, WORKBENCH_API_AUTHENTICATION_HASH_SALT).getBytes("UTF-8");
        } catch (Exception ex) {
            return loadString(properties, WORKBENCH_API_AUTHENTICATION_HASH_SALT).getBytes();
        }
    }

    /**
     * Returns the password to use for authentication token encryption
     */
    public byte[] getWorkbenchApiAuthenticationEncryptionPassword() {
        return _workbenchApiAuthenticationEncryptionPassword;
    }

    static final String WORKBENCH_API_AUTHENTICATION_ENCRYPTION_PASSWORD = "workbench.api.authentication.encyrption_password";

    private byte[] _workbenchApiAuthenticationEncryptionPassword = null;

    static byte[] loadWorkbenchApiAuthenticationEncryptionPassword(Properties properties) {
        try {
            return loadString(properties, WORKBENCH_API_AUTHENTICATION_ENCRYPTION_PASSWORD).getBytes("UTF-8");
        } catch (Exception ex) {
            return loadString(properties, WORKBENCH_API_AUTHENTICATION_ENCRYPTION_PASSWORD).getBytes();
        }
    }

    // *************************************************************************************************************
    // LOAD METHODS
    // *************************************************************************************************************

    /**
     * Loads a string. If it is blank (whitespace, empty or null), then exception is thrown.
     * 
     * @param properties
     *            Properties to lookup
     * @param name
     *            Name of the property
     * 
     * @return Value of the property named <code>name</code>.
     * @throws IllegalArgumentException
     *             if the value of the named properties is blank
     */
    private static String loadString(Properties properties, String name) {
        String s = properties.getProperty(name);
        if (StringUtils.isBlank(s)) {
            throw new IllegalArgumentException(
                    String.format("The property '%s' in '%s' is blank.'", name, APP_PROPERTY_FILE_NAME));
        }
        return s;
    }

    /**
     * Loads a string. If it is blank (whitespace, empty or null), then return the <code>defaultValue</code>
     * 
     * @param properties
     *            Properties to lookup
     * @param name
     *            Name of the property
     * @param defaultValue
     *            Value to return if property value is blank.
     * @return Value of the property named <code>name</code>. If whitespace, empty or null, then return the
     *         <code>defaultValue</code>
     */
    private static String loadString(Properties properties, String name, String defaultValue) {
        String s = properties.getProperty(name);
        if (StringUtils.isBlank(s)) {
            return defaultValue;
        }
        return s;
    }

    /**
     * Loads an int value. If not set, an exception is thrown
     * 
     * @param properties
     *            Properties to lookup
     * @param name
     *            Name of the property
     * 
     * @return Value of the property named <code>name</code>.
     * @throws IllegalArgumentException
     *             if the value of the named properties is blank
     */
    private static int loadInt(Properties properties, String name) {
        String s = loadString(properties, name);
        return Integer.parseInt(s);
    }

    /**
     * Loads an int value. If it is blank (whitespace, empty or null), then return the <code>defaultValue</code>
     * 
     * @param properties
     *            Properties to lookup
     * @param name
     *            Name of the property
     * @param defaultValue
     *            Value to return if property value is blank.
     * @return Value of the property named <code>name</code>. If whitespace, empty or null, then return the
     *         <code>defaultValue</code>
     */
    private static int loadInt(Properties properties, String name, int defaultValue) {
        String s = loadString(properties, name, null);
        if (s == null) {
            return defaultValue;
        }
        return Integer.parseInt(s);
    }

    /**
     * Loads a Long value. If it is blank (whitespace, empty or null), then return the <code>defaultValue</code>
     * 
     * @param properties
     *            Properties to lookup
     * @param name
     *            Name of the property
     * @param defaultValue
     *            Value to return if property value is blank.
     * @return Value of the property named <code>name</code>. If whitespace, empty or null, then return the
     *         <code>defaultValue</code>
     */
    private static long loadLong(Properties properties, String name, int defaultValue) {
        String s = loadString(properties, name, null);
        if (s == null) {
            return defaultValue;
        }
        return Long.parseLong(s);
    }

    /**
     * Loads an boolean value. If not set, an exception is thrown
     * 
     * @param properties
     *            Properties to lookup
     * @param name
     *            Name of the property
     * 
     * @return Value of the property named <code>name</code>.
     * @throws IllegalArgumentException
     *             if the value of the named properties is blank
     */
    @SuppressWarnings("unused")
    private static boolean loadBoolean(Properties properties, String name) {
        String s = loadString(properties, name);
        return Boolean.parseBoolean(s);
    }

    /**
     * Loads a boolean value. If it is blank (whitespace, empty or null), then return the <code>defaultValue</code>
     * 
     * @param properties
     *            Properties to lookup
     * @param name
     *            Name of the property
     * @param defaultValue
     *            Value to return if property value is blank.
     * @return Value of the property named <code>name</code>. If whitespace, empty or null, then return the
     *         <code>defaultValue</code>
     */
    private static boolean loadBoolean(Properties properties, String name, boolean defaultValue) {
        String s = loadString(properties, name, null);
        if (s == null) {
            return defaultValue;
        }
        return Boolean.parseBoolean(s);
    }

    /**
     * Returns a string representation of the parsed properties
     */
    public String toString() {
        StringBuilder sb = new StringBuilder();

        Class<AppProperties> cls = AppProperties.class;
        for (Field f : cls.getDeclaredFields()) {
            // Look for field names like APP_NAME
            String propertyNameFieldName = f.getName();
            if (!propertyNameFieldName.matches("^[A-Z0-9_]+$")) {
                continue;
            }

            // Build cache field (_appName) and method (loadAppName) methods
            String baseName = WordUtils.capitalizeFully(propertyNameFieldName, new char[] { '_' });
            baseName = baseName.replace("_", "");
            String cacheFieldName = "_" + StringUtils.uncapitalize(baseName);

            // If field not exist, then skip
            Field cacheField = null;
            try {
                cacheField = cls.getDeclaredField(cacheFieldName);
            } catch (NoSuchFieldException e) {
                continue;
            }

            // Get the value
            try {
                Object o = cacheField.get(this);
                sb.append(f.get(null));
                sb.append(" = ");
                sb.append(o == null ? "<not set>" : o.toString());
                sb.append("\n");
            } catch (Exception e) {
                sb.append("ERROR: Cannot load value for: " + propertyNameFieldName);
            }

        }

        return sb.toString();
    }
}