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

Java tutorial

Introduction

Here is the source code for org.chililog.server.common.BuildProperties.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.Properties;

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

/**
 * <p>
 * BuildProperties provides strongly typed access to build information in the <code>buildinfo.properties</code> file.
 * </p>
 * 
 * <p>
 * The <code>buildinfo.properties</code> file should be in the chililog-server*.jar
 * </p>
 * 
 * <h3>Example</h3>
 * 
 * <pre>
 * BuildProperties.getInstance().getBuildTimestamp();
 * </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 BuildProperties {

    private static Log4JLogger _logger = Log4JLogger.getLogger(BuildProperties.class);
    private static final String BUILDINFO_PROPERTY_FILE_NAME = "buildinfo.properties";

    /**
     * Returns the singleton instance for this class
     */
    public static BuildProperties 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 BuildProperties INSTANCE = new BuildProperties();
    }

    /**
     * <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 BuildProperties() {
        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> loads the settings form the <code>buildinfo.properties</code> file found in the
     * classpath
     * </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 = BuildProperties.class.getClassLoader()
                    .getResourceAsStream(BUILDINFO_PROPERTY_FILE_NAME);
            if (is == null) {
                throw new FileNotFoundException("'buildinfo.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<BuildProperties> cls = BuildProperties.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;
    }

    /**
     * Returns this application's name - ChiliLog Server.
     */
    public String getAppName() {
        return _appName;
    }

    static final String APP_NAME = "application.name";

    private String _appName = null;

    static String loadAppName(Properties properties) {
        return loadString(properties, APP_NAME, "ChiliLog Server");
    }

    /**
     * Returns this application's version
     */
    public String getAppVersion() {
        return _appVersion;
    }

    static final String APP_VERSION = "application.version";

    private String _appVersion = null;

    static String loadAppVersion(Properties properties) {
        return loadString(properties, APP_VERSION);
    }

    /**
     * Returns the date and time when this application build was performed
     */
    public String getBuildTimestamp() {
        return _buildTimestamp;
    }

    static final String BUILD_TIMESTAMP = "build.timestamp";

    private String _buildTimestamp = null;

    static String loadBuildTimestamp(Properties properties) {
        return loadString(properties, BUILD_TIMESTAMP);
    }

    /**
     * Returns the name of machine on which this application build was performed
     */
    public String getBuildMachineName() {
        return _buildMachineName;
    }

    static final String BUILD_MACHINE_NAME = "build.machinename";

    private String _buildMachineName = null;

    static String loadBuildMachineName(Properties properties) {
        return loadString(properties, BUILD_MACHINE_NAME);
    }

    /**
     * Returns the user account with which this application build was performed
     */
    public String getBuildUserName() {
        return _buildUserName;
    }

    static final String BUILD_USER_NAME = "build.username";

    private String _buildUserName = null;

    static String loadBuildUserName(Properties properties) {
        return loadString(properties, BUILD_USER_NAME);
    }

    // *************************************************************************************************************
    // 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, BUILDINFO_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;
    }

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

        Class<BuildProperties> cls = BuildProperties.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();
    }
}