gda.configuration.properties.JakartaPropertiesConfig.java Source code

Java tutorial

Introduction

Here is the source code for gda.configuration.properties.JakartaPropertiesConfig.java

Source

/*-
 * Copyright  2009 Diamond Light Source Ltd., Science and Technology
 * Facilities Council Daresbury Laboratory
 *
 * This file is part of GDA.
 *
 * GDA is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License version 3 as published by the Free
 * Software Foundation.
 *
 * GDA is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along
 * with GDA. If not, see <http://www.gnu.org/licenses/>.
 */

package gda.configuration.properties;

import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.configuration.CompositeConfiguration;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.ConfigurationFactory;
import org.apache.commons.configuration.DataConfiguration;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.configuration.SystemConfiguration;
import org.apache.commons.configuration.XMLConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Properties Configuration class, implementing PropertiesConfig interface. Uses the Jakarta Commons Configuration
 * package for its implementation. It insulates client code from the details of using Jakarta.
 * <p>
 * <p>
 * The class creates a composite configuration object on startup. Clients call loadPropertyData() to add property data
 * sources. This method can be passed with the name of either a Jakarta descriptor file (file name must end with
 * "config.xml"), or a Jakarta XML property file (ending in ".xml") or a legacy java properties file (file name must end
 * with ".properties").
 * <p>
 * <p>
 * Each call to loadPropertyData adds a new group of properties to the composite configuration object.
 * <p>
 * <p>
 * N.B. The order of loading properties is important, as this affects the order of searching for keys when querying a
 * property value. If duplicate keys exist, then the first key found has its value returned.
 * <p>
 * <p>
 * So user overrides should be loaded first. Plugin/technique-specific properties should be loaded next. Default/core
 * property data should be loaded last.
 * <p>
 */
public class JakartaPropertiesConfig implements PropertiesConfig {

    private static final Logger logger = LoggerFactory.getLogger(JakartaPropertiesConfig.class);

    // all properties config data is combined into this object
    private CompositeConfiguration config = null;

    // README - this map may not be needed - was for debugging initially.
    private Map<String, Configuration> configMap = null;

    /**
     * Constructor for JakartaPropertiesConfig objects. Creates a new composite configuration and adds a system
     * configuration to it.
     */
    public JakartaPropertiesConfig() {
        // create global composite to store all loaded property config data
        config = new CompositeConfiguration();

        // create a system properties configuration - grabs all system
        // properties.
        Configuration sysConfig = new SystemConfiguration();
        config.addConfiguration(sysConfig);

        // create map to store individual configs
        configMap = new HashMap<String, Configuration>();

        // put system properties in the map
        configMap.put("system", sysConfig);
    }

    /**
     * Remove all cached property information and reload system properties. User must reload any required property data
     * sources.
     */
    public void ResetProperties() {
        // clear out composite config and add a fresh system config into it
        config.clear();
        Configuration sysConfig = new SystemConfiguration();
        config.addConfiguration(sysConfig);

        // clear out the config map and put system into it
        configMap.clear();
        configMap.put("system", sysConfig);
    }

    /**
     * Workaround for an apparent bug with factory base path default of ".". Configuration descriptor file (config.xml)
     * contains references to one or more property data sources. Files should work with relative pathnames. However just
     * specifying "java.properties" doesnt find the file! So need to fixup the factory base path with the location of
     * the config.xml file. This appears to fix the problem - eg "java.properties" is now found.
     * 
     * @param factory
     *            the current configuration factory in use
     * @param listName
     *            file path name of the property data source
     */
    private void configFactoryBasePathBugWorkaround(ConfigurationFactory factory, String listName) {
        // String basePath = factory.getBasePath(); // retain for debugging

        // work out length of path excluding filename
        int pathEndIndex = 0;
        pathEndIndex = Math.max(pathEndIndex, listName.lastIndexOf('\\'));
        pathEndIndex = Math.max(pathEndIndex, listName.lastIndexOf('/'));

        // set the path into factory, since its default of "." doesnt
        // successfully fetch files from the same folder as config.xml!!
        String p = listName.substring(0, pathEndIndex);
        factory.setBasePath(p);

        // String setBasePath = factory.getBasePath(); // retain for debugging
    }

    /**
     * Load in a single property data source, or a "config.xml" descriptor file (specifying multiple data sources).
     * Properties are added to the global composite property data set.
     * <p>
     * <p>
     * N.B. Due to underlying Jakarta (commons configuration) implementation, any previously loaded properties (with
     * duplicate keys) will override subsequently loaded properties. ie clients should load in override (eg user) data
     * before loading default (eg core) data.
     * <p>
     * <p>
     * N.B. Also system properties are currently loaded before any calls to loadPropertyData, so they take precedence
     * over everything.
     * 
     * @param listName
     *            the path name of the property data source. If the name ends in "config.xml" then the file is treated
     *            as a Jakarta commons configuration descriptor file. If the file ends in ".xml" it is loaded as a
     *            Jakarta XML property configuration file. Otherwise it is loaded as a legacy flat-file key-value pair
     *            plain text property file. N.B. Although Jakarta supports other data sources, eg JDBC, these are not
     *            yet supported via this method.
     * @throws ConfigurationException
     * @see gda.configuration.properties.PropertiesConfig#loadPropertyData(java.lang.String)
     */
    @Override
    public void loadPropertyData(String listName) throws ConfigurationException {
        Configuration userConfig = null;

        if (listName.contains(".xml")) {
            if (listName.endsWith("config.xml")) {
                // create a JCC configuration factory from a JCC config
                // descriptor
                // file and make a JCC configuration interface/object from it
                ConfigurationFactory factory = new ConfigurationFactory();

                // README - fix to get relative paths in config.xml working.
                // See comment for this method for explanation.
                configFactoryBasePathBugWorkaround(factory, listName);

                // now try to load in config.xml - relative paths should now
                // work
                factory.setConfigurationFileName(listName);
                userConfig = factory.getConfiguration();
            } else {
                // load a JCC XML-format property file
                userConfig = new XMLConfiguration(listName);
            }
        } else {
            if (listName.contains(".properties")) {
                // load a classic java properties flat-textfile,
                // containing just name-value pairs - with extended JCC syntax
                userConfig = new PropertiesConfiguration(listName);
            }
        }

        if (userConfig != null) {
            config.addConfiguration(userConfig);
            configMap.put(listName, userConfig);
        }
    }

    /**
     * Dump out all existing properties to message logging debug channel. N.B. Forces debug level to maximum, to force
     * property values to be printed to debug channel. Since we can't fetch the debug level, any existing level is
     * overwritten and cannot be restored. However this method is intended for debugging purposes only.
     */
    @Override
    public void dumpProperties() {

        ((ch.qos.logback.classic.Logger) logger).setLevel(ch.qos.logback.classic.Level.DEBUG);

        @SuppressWarnings("unchecked")
        Iterator<String> keyIterator = config.getKeys();

        while (keyIterator.hasNext()) {
            String key = keyIterator.next();

            Object o = config.getProperty(key);
            if (o != null) {
                if (o instanceof String) {
                    // Calling getString method ensures value has any
                    // processing
                    // done by commons config applied - ie string
                    // interpolation, etc.
                    logger.debug(key + " = " + LocalProperties.get(key));
                } else {
                    // Handle non-string objects, eg ArrayList's
                    logger.debug(key + " = " + o.toString());
                }
            }
        }
    }

    @Override
    public int getInteger(String name, int defaultValue) {
        return config.getInt(name, defaultValue);
    }

    @Override
    public String getString(String name, String defaultValue) {
        return config.getString(name, defaultValue);
    }

    @Override
    public float getFloat(String name, float defaultValue) {
        return config.getFloat(name, defaultValue);
    }

    @Override
    public double getDouble(String name, double defaultValue) {
        return config.getDouble(name, defaultValue);
    }

    @Override
    public boolean getBoolean(String name, boolean defaultValue) {
        return config.getBoolean(name, defaultValue);
    }

    /**
     * Get a file pathname. Any backslashes found are converted to forward slashes. N.B. We know this is a file
     * pathname. Backslashes can cause problems with Java code expecting forward slashes. So it *should* be safe to
     * convert any backslashes to forward slashes. If any client code needs to fetch properties with backslashes (eg to
     * pass to native JNI code), then either convert the result back to forward slashes, or just call getString instead.
     * 
     * @see gda.configuration.properties.PropertiesConfig#getPath(java.lang.String, java.lang.String)
     */
    @Override
    public String getPath(String name, String defaultValue) {
        String value = config.getString(name, defaultValue);

        if (value != null) {
            value = value.replace('\\', '/');
        }

        return value;
    }

    @Override
    public URL getURL(String name, URL defaultValue) {
        // Use decorator, which can convert to URLs, Locales, Dates, Colours,
        // etc.
        DataConfiguration dataConfig = new DataConfiguration(config);

        return dataConfig.getURL(name, defaultValue);
    }

    @Override
    public void setString(String value, String name) {
        config.setProperty(name, value);
    }

    @Override
    public void clearProperty(String key) {
        config.clearProperty(key);
    }

    @Override
    public boolean containsKey(String key) {
        return config.containsKey(key);
    }

    @Override
    public String[] getStringArray(String propertyName) {
        return config.getStringArray(propertyName);
    }

}