com.mpaike.core.config.xml.XMLConfigService.java Source code

Java tutorial

Introduction

Here is the source code for com.mpaike.core.config.xml.XMLConfigService.java

Source

/*
 * Copyright (C) 2009-2010 WWF Software Limited.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
    
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
    
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
    
 * As a special exception to the terms and conditions of version 2.0 of 
 * the GPL, you may redistribute this Program in connection with Free/Libre 
 * and Open Source Software ("FLOSS") applications as described in WWF's 
 * FLOSS exception.  You should have recieved a copy of the text describing 
 * the FLOSS exception, and it is also available here: 
 * http://www.42y.net/legal/licensing"
 */
package com.mpaike.core.config.xml;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.Constants;
import org.springframework.core.io.Resource;

import com.mpaike.core.config.BaseConfigService;
import com.mpaike.core.config.ConfigDeployer;
import com.mpaike.core.config.ConfigDeployment;
import com.mpaike.core.config.ConfigElement;
import com.mpaike.core.config.ConfigException;
import com.mpaike.core.config.ConfigSection;
import com.mpaike.core.config.ConfigSectionImpl;
import com.mpaike.core.config.ConfigSource;
import com.mpaike.core.config.evaluator.Evaluator;
import com.mpaike.core.config.xml.elementreader.ConfigElementReader;
import com.mpaike.core.config.xml.elementreader.GenericElementReader;

/**
 * XML based configuration service.
 * <p/>
 * The sytem properties can be used; to override entries in the properties files, act as fallback values or be ignored.
 * <ul>
 *   <li><b>SYSTEM_PROPERTIES_MODE_NEVER:             </b>Don't use system properties at all.</li>
 *   <li><b>SYSTEM_PROPERTIES_MODE_FALLBACK:          </b>Fallback to a system property only for undefined properties.</li>
 *   <li><b>SYSTEM_PROPERTIES_MODE_OVERRIDE: (DEFAULT)</b>Use a system property if it is available.</li>
 * </ul>
 * 
 */
public class XMLConfigService extends BaseConfigService implements XMLConfigConstants {
    private static final Log logger = LogFactory.getLog(XMLConfigService.class);

    private static final Constants constants = new Constants(PropertyPlaceholderConfigurer.class);

    private Resource[] propertyLocations;
    private int systemPropertiesMode = PropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_OVERRIDE;
    private PropertyConfigurer propertyConfigurer;
    private Map<String, ConfigElementReader> elementReaders;

    /**
     * Constructs an XMLConfigService using the given config source
     * 
     * @param configSource
     *            A ConfigSource
     */
    public XMLConfigService(ConfigSource configSource) {
        super(configSource);
    }

    /**
     * Set locations of properties files to be loaded.
     * <p>Can point to classic properties files or to XML files
     * that follow JDK 1.5's properties XML format.
     */
    public void setProperties(Resource[] locations) {
        this.propertyLocations = locations;
    }

    /**
     * Set the system property mode by the name of the corresponding constant,
     * e.g. "SYSTEM_PROPERTIES_MODE_OVERRIDE".
     * @param constantName name of the constant
     * @throws java.lang.IllegalArgumentException if an invalid constant was specified
     * @see #setSystemPropertiesMode
     */
    public void setSystemPropertiesModeName(String constantName) throws IllegalArgumentException {
        this.systemPropertiesMode = constants.asNumber(constantName).intValue();
    }

    public List<ConfigDeployment> initConfig() {
        if (logger.isDebugEnabled())
            logger.debug("Commencing initialisation");

        List<ConfigDeployment> configDeployments = super.initConfig();

        // initialise property configurer
        propertyConfigurer = null;
        if (propertyLocations != null) {
            PropertyConfigurer configurer = new PropertyConfigurer();
            configurer.setLocations(propertyLocations);
            configurer.setIgnoreUnresolvablePlaceholders(true);
            configurer.setSystemPropertiesMode(systemPropertiesMode);
            configurer.init();
            propertyConfigurer = configurer;
        }

        // initialise the element readers map with built-in readers
        putElementReaders(new HashMap<String, ConfigElementReader>());

        List<ConfigDeployment> deployments = parse();
        configDeployments.addAll(deployments);

        // append additional config, if any
        for (ConfigDeployer configDeployer : configDeployers) {
            deployments = configDeployer.initConfig();
            configDeployments.addAll(deployments);
        }

        if (logger.isDebugEnabled())
            logger.debug("Completed initialisation");

        return configDeployments;
    }

    public void destroy() {
        removeElementReaders();
        super.destroy();
    }

    protected void parse(InputStream stream) {
        Map<String, ConfigElementReader> parsedElementReaders = null;
        Map<String, Evaluator> parsedEvaluators = null;
        List<ConfigSection> parsedConfigSections = new ArrayList<ConfigSection>();

        String currentArea = null;
        try {
            // get the root element
            SAXReader reader = new SAXReader();
            Document document = reader.read(stream);
            Element rootElement = document.getRootElement();

            // see if there is an area defined
            currentArea = rootElement.attributeValue("area");

            // parse the plug-ins section of a config file
            Element pluginsElement = rootElement.element(ELEMENT_PLUG_INS);
            if (pluginsElement != null) {
                // parse the evaluators section
                parsedEvaluators = parseEvaluatorsElement(pluginsElement.element(ELEMENT_EVALUATORS));

                // parse the element readers section
                parsedElementReaders = parseElementReadersElement(pluginsElement.element(ELEMENT_ELEMENT_READERS));
            }

            // parse each config section in turn
            @SuppressWarnings("unchecked")
            Iterator<Element> configElements = rootElement.elementIterator(ELEMENT_CONFIG);
            while (configElements.hasNext()) {
                Element configElement = configElements.next();
                parsedConfigSections.add(parseConfigElement(parsedElementReaders, configElement, currentArea));
            }
        } catch (Throwable e) {
            if (e instanceof ConfigException) {
                throw (ConfigException) e;
            } else {
                throw new ConfigException("Failed to parse config stream", e);
            }
        }

        try {
            // valid for this stream, now add to config service ...

            if (parsedEvaluators != null) {
                for (Map.Entry<String, Evaluator> entry : parsedEvaluators.entrySet()) {
                    // add the evaluators to the config service
                    addEvaluator(entry.getKey(), entry.getValue());
                }
            }

            if (parsedElementReaders != null) {
                for (Map.Entry<String, ConfigElementReader> entry : parsedElementReaders.entrySet()) {
                    // add the element readers to the config service
                    addConfigElementReader(entry.getKey(), entry.getValue());
                }
            }

            if (parsedConfigSections != null) {
                for (ConfigSection section : parsedConfigSections) {
                    // add the config sections to the config service
                    addConfigSection(section, currentArea);
                }
            }
        } catch (Throwable e) {
            throw new ConfigException("Failed to add config to config service", e);
        }
    }

    /**
     * Parses the evaluators element
     * 
     * @param evaluatorsElement
     */
    private Map<String, Evaluator> parseEvaluatorsElement(Element evaluatorsElement) {
        if (evaluatorsElement != null) {
            Map<String, Evaluator> parsedEvaluators = new HashMap<String, Evaluator>();
            @SuppressWarnings("unchecked")
            Iterator<Element> evaluators = evaluatorsElement.elementIterator();
            while (evaluators.hasNext()) {
                Element evaluatorElement = evaluators.next();
                String evaluatorName = evaluatorElement.attributeValue(ATTR_ID);
                String evaluatorClass = evaluatorElement.attributeValue(ATTR_CLASS);

                // TODO: Can these checks be removed if we use a DTD and/or
                // schema??
                if (evaluatorName == null || evaluatorName.length() == 0) {
                    throw new ConfigException("All evaluator elements must define an id attribute");
                }

                if (evaluatorClass == null || evaluatorClass.length() == 0) {
                    throw new ConfigException("Evaluator '" + evaluatorName + "' must define a class attribute");
                }

                // add the evaluator
                parsedEvaluators.put(evaluatorName, createEvaluator(evaluatorName, evaluatorClass));
            }

            return parsedEvaluators;
        }

        return null;
    }

    /**
     * Parses the element-readers element
     * 
     * @param readersElement
     */
    private Map<String, ConfigElementReader> parseElementReadersElement(Element readersElement) {
        if (readersElement != null) {
            Map<String, ConfigElementReader> parsedElementReaders = new HashMap<String, ConfigElementReader>();
            @SuppressWarnings("unchecked")
            Iterator<Element> readers = readersElement.elementIterator();
            while (readers.hasNext()) {
                Element readerElement = readers.next();
                String readerElementName = readerElement.attributeValue(ATTR_ELEMENT_NAME);
                String readerElementClass = readerElement.attributeValue(ATTR_CLASS);

                if (readerElementName == null || readerElementName.length() == 0) {
                    throw new ConfigException("All element-reader elements must define an element-name attribute");
                }

                if (readerElementClass == null || readerElementClass.length() == 0) {
                    throw new ConfigException(
                            "Element-reader '" + readerElementName + "' must define a class attribute");
                }

                // add the element reader
                parsedElementReaders.put(readerElementName,
                        createConfigElementReader(readerElementName, readerElementClass));
            }

            return parsedElementReaders;
        }

        return null;
    }

    /**
     * Parses a config element of a config file
     * 
     * @param configElement The config element
     * @param currentArea The current area
     */
    private ConfigSection parseConfigElement(Map<String, ConfigElementReader> parsedElementReaders,
            Element configElement, String currentArea) {
        if (configElement != null) {
            boolean replace = false;
            String evaluatorName = configElement.attributeValue(ATTR_EVALUATOR);
            String condition = configElement.attributeValue(ATTR_CONDITION);
            String replaceValue = configElement.attributeValue(ATTR_REPLACE);
            if (replaceValue != null && replaceValue.equalsIgnoreCase("true")) {
                replace = true;
            }

            // create the section object
            ConfigSectionImpl section = new ConfigSectionImpl(evaluatorName, condition, replace);

            // retrieve the config elements for the section
            @SuppressWarnings("unchecked")
            Iterator<Element> children = configElement.elementIterator();
            while (children.hasNext()) {
                Element child = children.next();
                String elementName = child.getName();

                // get the element reader for the child
                ConfigElementReader elementReader = null;
                if (parsedElementReaders != null) {
                    elementReader = parsedElementReaders.get(elementName);
                }

                if (elementReader == null) {
                    elementReader = getConfigElementReader(elementName);
                }

                if (logger.isDebugEnabled())
                    logger.debug("Retrieved element reader " + elementReader + " for element named '" + elementName
                            + "'");

                if (elementReader == null) {
                    elementReader = new GenericElementReader(propertyConfigurer);

                    if (logger.isDebugEnabled())
                        logger.debug("Defaulting to " + elementReader + " as there wasn't an element "
                                + "reader registered for element '" + elementName + "'");
                }

                ConfigElement cfgElement = elementReader.parse(child);
                section.addConfigElement(cfgElement);

                if (logger.isDebugEnabled())
                    logger.debug("Added " + cfgElement + " to " + section);
            }

            return section;
        }

        return null;
    }

    /**
     * Adds the config element reader to the config service
     * 
     * @param name
     *            Name of the element
     * @param elementReader
     *            The element reader
     */
    private void addConfigElementReader(String elementName, ConfigElementReader elementReader) {
        putConfigElementReader(elementName, elementReader);

        if (logger.isDebugEnabled())
            logger.debug("Added element reader '" + elementName + "': " + elementReader.getClass().getName());
    }

    /**
     * Instantiate the config element reader with the given name and class
     * 
     * @param name
     *            Name of the element
     * @param className
     *            Class name of the element reader
     */
    private ConfigElementReader createConfigElementReader(String elementName, String className) {
        ConfigElementReader elementReader = null;

        try {
            @SuppressWarnings("unchecked")
            Class clazz = Class.forName(className);
            elementReader = (ConfigElementReader) clazz.newInstance();
        } catch (Throwable e) {
            throw new ConfigException(
                    "Could not instantiate element reader for '" + elementName + "' with class: " + className, e);

        }

        return elementReader;
    }

    /**
     * Gets the element reader from the in-memory 'cache' for the given element name
     * 
     * @param elementName Name of the element to get the reader for
     * @return ConfigElementReader object or null if it doesn't exist
     */
    private ConfigElementReader getConfigElementReader(String elementName) {
        return (ConfigElementReader) getElementReaders().get(elementName);
    }

    /**
     * Put the config element reader into the in-memory 'cache' for the given element name
     * 
     * @param elementName
     * @param elementReader
     */
    private void putConfigElementReader(String elementName, ConfigElementReader elementReader) {
        getElementReaders().put(elementName, elementReader);
    }

    /**
     * Get the elementReaders from the in-memory 'cache'
     * 
     * @return elementReaders
     */
    protected Map<String, ConfigElementReader> getElementReaders() {
        return elementReaders;
    }

    /**
     * Put the elementReaders into the in-memory 'cache'
     * 
     * @param elementReaders
     */
    protected void putElementReaders(Map<String, ConfigElementReader> elementReaders) {
        this.elementReaders = elementReaders;
    }

    /**
     * Remove the elementReaders from the in-memory 'cache'
     */
    protected void removeElementReaders() {
        elementReaders.clear();
        elementReaders = null;
    }

    /**
     * Provides access to property values 
     */
    public static class PropertyConfigurer extends PropertyPlaceholderConfigurer {
        private Properties properties;

        /**
         * Initialise
         */
        /*package*/ void init() {
            try {
                properties = mergeProperties();
            } catch (IOException e) {
                throw new ConfigException("Failed to retrieve properties", e);
            }
        }

        /**
         * Replace placeholders with values
         * 
         * @param value
         * @return resolved value
         */
        public String resolveValue(String val) {
            return parseStringValue(val, properties, new HashSet<Object>());
        }
    }

}