blob: a902d16b37bc7c28f23e8471765ac47838a384e6 [file] [log] [blame]
/*
* 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.commons.configuration;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import org.apache.commons.configuration.plist.PropertyListConfiguration;
import org.apache.commons.configuration.plist.XMLPropertyListConfiguration;
import org.apache.commons.digester.AbstractObjectCreationFactory;
import org.apache.commons.digester.CallMethodRule;
import org.apache.commons.digester.Digester;
import org.apache.commons.digester.ObjectCreationFactory;
import org.apache.commons.digester.Substitutor;
import org.apache.commons.digester.substitution.MultiVariableExpander;
import org.apache.commons.digester.substitution.VariableSubstitutor;
import org.apache.commons.digester.xmlrules.DigesterLoader;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
/**
* <p>
* Factory class to create a CompositeConfiguration from a .xml file using
* Digester. By default it can handle the Configurations from commons-
* configuration. If you need to add your own, then you can pass in your own
* digester rules to use. It is also namespace aware, by providing a
* digesterRuleNamespaceURI.
* </p>
* <p>
* <em>Note:</em> Almost all of the features provided by this class and many
* more are also available for the <code>{@link DefaultConfigurationBuilder}</code>
* class. <code>DefaultConfigurationBuilder</code> also has a more robust
* merge algorithm for constructing combined configurations. So it is
* recommended to use this class instead of <code>ConfigurationFactory</code>.
* </p>
*
* @author <a href="mailto:epugh@upstate.com">Eric Pugh</a>
* @author <a href="mailto:hps@intermeta.de">Henning P. Schmiedehausen</a>
* @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
* @version $Id$
*/
public class ConfigurationFactory
{
/** Constant for the root element in the info file.*/
private static final String SEC_ROOT = "configuration/";
/** Constant for the override section.*/
private static final String SEC_OVERRIDE = SEC_ROOT + "override/";
/** Constant for the additional section.*/
private static final String SEC_ADDITIONAL = SEC_ROOT + "additional/";
/** Constant for the optional attribute.*/
private static final String ATTR_OPTIONAL = "optional";
/** Constant for the fileName attribute.*/
private static final String ATTR_FILENAME = "fileName";
/** Constant for the load method.*/
private static final String METH_LOAD = "load";
/** Constant for the default base path (points to actual directory).*/
private static final String DEF_BASE_PATH = ".";
/** static logger */
private static Log log = LogFactory.getLog(ConfigurationFactory.class);
/** The XML file with the details about the configuration to load */
private String configurationFileName;
/** The URL to the XML file with the details about the configuration to load. */
private URL configurationURL;
/**
* The implicit base path for included files. This path is determined by
* the configuration to load and used unless no other base path was
* explicitely specified.
*/
private String implicitBasePath;
/** The basePath to prefix file paths for file based property files. */
private String basePath;
/** URL for xml digester rules file */
private URL digesterRules;
/** The digester namespace to parse */
private String digesterRuleNamespaceURI;
/**
* Constructor
*/
public ConfigurationFactory()
{
setBasePath(DEF_BASE_PATH);
}
/**
* Constructor with ConfigurationFile Name passed
*
* @param configurationFileName The path to the configuration file
*/
public ConfigurationFactory(String configurationFileName)
{
setConfigurationFileName(configurationFileName);
}
/**
* Return the configuration provided by this factory. It loads the
* configuration file which is a XML description of the actual
* configurations to load. It can contain various different types of
* configuration, e.g. Properties, XML and JNDI.
*
* @return A Configuration object
* @throws ConfigurationException A generic exception that we had trouble during the
* loading of the configuration data.
*/
public Configuration getConfiguration() throws ConfigurationException
{
Digester digester;
InputStream input = null;
ConfigurationBuilder builder = new ConfigurationBuilder();
URL url = getConfigurationURL();
try
{
if (url == null)
{
url = ConfigurationUtils.locate(implicitBasePath, getConfigurationFileName());
}
input = url.openStream();
}
catch (Exception e)
{
log.error("Exception caught opening stream to URL", e);
throw new ConfigurationException("Exception caught opening stream to URL", e);
}
if (getDigesterRules() == null)
{
digester = new Digester();
configureNamespace(digester);
initDefaultDigesterRules(digester);
}
else
{
digester = DigesterLoader.createDigester(getDigesterRules());
// This might already be too late. As far as I can see, the namespace
// awareness must be configured before the digester rules are loaded.
configureNamespace(digester);
}
// Configure digester to always enable the context class loader
digester.setUseContextClassLoader(true);
// Add a substitutor to resolve system properties
enableDigesterSubstitutor(digester);
// Put the composite builder object below all of the other objects.
digester.push(builder);
// Parse the input stream to configure our mappings
try
{
digester.parse(input);
input.close();
}
catch (SAXException saxe)
{
log.error("SAX Exception caught", saxe);
throw new ConfigurationException("SAX Exception caught", saxe);
}
catch (IOException ioe)
{
log.error("IO Exception caught", ioe);
throw new ConfigurationException("IO Exception caught", ioe);
}
return builder.getConfiguration();
}
/**
* Returns the configurationFile.
*
* @return The name of the configuration file. Can be null.
*/
public String getConfigurationFileName()
{
return configurationFileName;
}
/**
* Sets the configurationFile.
*
* @param configurationFileName The name of the configurationFile to use.
*/
public void setConfigurationFileName(String configurationFileName)
{
File file = new File(configurationFileName).getAbsoluteFile();
this.configurationFileName = file.getName();
implicitBasePath = file.getParent();
}
/**
* Returns the URL of the configuration file to be loaded.
*
* @return the URL of the configuration to load
*/
public URL getConfigurationURL()
{
return configurationURL;
}
/**
* Sets the URL of the configuration to load. This configuration can be
* either specified by a file name or by a URL.
*
* @param url the URL of the configuration to load
*/
public void setConfigurationURL(URL url)
{
configurationURL = url;
implicitBasePath = url.toString();
}
/**
* Returns the digesterRules.
*
* @return URL
*/
public URL getDigesterRules()
{
return digesterRules;
}
/**
* Sets the digesterRules.
*
* @param digesterRules The digesterRules to set
*/
public void setDigesterRules(URL digesterRules)
{
this.digesterRules = digesterRules;
}
/**
* Adds a substitutor to interpolate system properties
*
* @param digester The digester to which we add the substitutor
*/
protected void enableDigesterSubstitutor(Digester digester)
{
Map systemProperties = System.getProperties();
MultiVariableExpander expander = new MultiVariableExpander();
expander.addSource("$", systemProperties);
// allow expansion in both xml attributes and element text
Substitutor substitutor = new VariableSubstitutor(expander);
digester.setSubstitutor(substitutor);
}
/**
* Initializes the parsing rules for the default digester
*
* This allows the Configuration Factory to understand the default types:
* Properties, XML and JNDI. Two special sections are introduced:
* <code>&lt;override&gt;</code> and <code>&lt;additional&gt;</code>.
*
* @param digester The digester to configure
*/
protected void initDefaultDigesterRules(Digester digester)
{
initDigesterSectionRules(digester, SEC_ROOT, false);
initDigesterSectionRules(digester, SEC_OVERRIDE, false);
initDigesterSectionRules(digester, SEC_ADDITIONAL, true);
}
/**
* Sets up digester rules for a specified section of the configuration
* info file.
*
* @param digester the current digester instance
* @param matchString specifies the section
* @param additional a flag if rules for the additional section are to be
* added
*/
protected void initDigesterSectionRules(Digester digester, String matchString, boolean additional)
{
setupDigesterInstance(
digester,
matchString + "properties",
new PropertiesConfigurationFactory(),
METH_LOAD,
additional);
setupDigesterInstance(
digester,
matchString + "plist",
new PropertyListConfigurationFactory(),
METH_LOAD,
additional);
setupDigesterInstance(
digester,
matchString + "xml",
new FileConfigurationFactory(XMLConfiguration.class),
METH_LOAD,
additional);
setupDigesterInstance(
digester,
matchString + "hierarchicalXml",
new FileConfigurationFactory(XMLConfiguration.class),
METH_LOAD,
additional);
setupDigesterInstance(
digester,
matchString + "jndi",
new JNDIConfigurationFactory(),
null,
additional);
setupDigesterInstance(
digester,
matchString + "system",
new SystemConfigurationFactory(),
null,
additional);
}
/**
* Sets up digester rules for a configuration to be loaded.
*
* @param digester the current digester
* @param matchString the pattern to match with this rule
* @param factory an ObjectCreationFactory instance to use for creating new
* objects
* @param method the name of a method to be called or <b>null</b> for none
* @param additional a flag if rules for the additional section are to be
* added
*/
protected void setupDigesterInstance(
Digester digester,
String matchString,
ObjectCreationFactory factory,
String method,
boolean additional)
{
if (additional)
{
setupUnionRules(digester, matchString);
}
digester.addFactoryCreate(matchString, factory);
digester.addSetProperties(matchString);
if (method != null)
{
digester.addRule(matchString, new CallOptionalMethodRule(method));
}
digester.addSetNext(matchString, "addConfiguration", Configuration.class.getName());
}
/**
* Sets up rules for configurations in the additional section.
*
* @param digester the current digester
* @param matchString the pattern to match with this rule
*/
protected void setupUnionRules(Digester digester, String matchString)
{
digester.addObjectCreate(matchString,
AdditionalConfigurationData.class);
digester.addSetProperties(matchString);
digester.addSetNext(matchString, "addAdditionalConfig",
AdditionalConfigurationData.class.getName());
}
/**
* Returns the digesterRuleNamespaceURI.
*
* @return A String with the digesterRuleNamespaceURI.
*/
public String getDigesterRuleNamespaceURI()
{
return digesterRuleNamespaceURI;
}
/**
* Sets the digesterRuleNamespaceURI.
*
* @param digesterRuleNamespaceURI The new digesterRuleNamespaceURI to use
*/
public void setDigesterRuleNamespaceURI(String digesterRuleNamespaceURI)
{
this.digesterRuleNamespaceURI = digesterRuleNamespaceURI;
}
/**
* Configure the current digester to be namespace aware and to have
* a Configuration object to which all of the other configurations
* should be added
*
* @param digester The Digester to configure
*/
private void configureNamespace(Digester digester)
{
if (getDigesterRuleNamespaceURI() != null)
{
digester.setNamespaceAware(true);
digester.setRuleNamespaceURI(getDigesterRuleNamespaceURI());
}
else
{
digester.setNamespaceAware(false);
}
digester.setValidating(false);
}
/**
* Returns the Base path from which this Configuration Factory operates.
* This is never null. If you set the BasePath to null, then a base path
* according to the configuration to load is returned.
*
* @return The base Path of this configuration factory.
*/
public String getBasePath()
{
String path = StringUtils.isEmpty(basePath)
|| DEF_BASE_PATH.equals(basePath) ? implicitBasePath : basePath;
return StringUtils.isEmpty(path) ? DEF_BASE_PATH : path;
}
/**
* Sets the basePath for all file references from this Configuration Factory.
* Normally a base path need not to be set because it is determined by
* the location of the configuration file to load. All relative pathes in
* this file are resolved relative to this file. Setting a base path makes
* sense if such relative pathes should be otherwise resolved, e.g. if
* the configuration file is loaded from the class path and all sub
* configurations it refers to are stored in a special config directory.
*
* @param basePath The new basePath to set.
*/
public void setBasePath(String basePath)
{
this.basePath = basePath;
}
/**
* A base class for digester factory classes. This base class maintains
* a default class for the objects to be created.
* There will be sub classes for specific configuration implementations.
*/
public class DigesterConfigurationFactory extends AbstractObjectCreationFactory
{
/** Actual class to use. */
private Class clazz;
/**
* Creates a new instance of <code>DigesterConfigurationFactory</code>.
*
* @param clazz the class which we should instantiate
*/
public DigesterConfigurationFactory(Class clazz)
{
this.clazz = clazz;
}
/**
* Creates an instance of the specified class.
*
* @param attribs the attributes (ignored)
* @return the new object
* @throws Exception if object creation fails
*/
public Object createObject(Attributes attribs) throws Exception
{
return clazz.newInstance();
}
}
/**
* A tiny inner class that allows the Configuration Factory to
* let the digester construct FileConfiguration objects
* that already have the correct base Path set.
*
*/
public class FileConfigurationFactory extends DigesterConfigurationFactory
{
/**
* C'tor
*
* @param clazz The class which we should instantiate.
*/
public FileConfigurationFactory(Class clazz)
{
super(clazz);
}
/**
* Gets called by the digester.
*
* @param attributes the actual attributes
* @return the new object
* @throws Exception Couldn't instantiate the requested object.
*/
public Object createObject(Attributes attributes) throws Exception
{
FileConfiguration conf = createConfiguration(attributes);
conf.setBasePath(getBasePath());
return conf;
}
/**
* Creates the object, a <code>FileConfiguration</code>.
*
* @param attributes the actual attributes
* @return the file configuration
* @throws Exception if the object could not be created
*/
protected FileConfiguration createConfiguration(Attributes attributes) throws Exception
{
return (FileConfiguration) super.createObject(attributes);
}
}
/**
* A factory that returns an XMLPropertiesConfiguration for .xml files
* and a PropertiesConfiguration for the others.
*
* @since 1.2
*/
public class PropertiesConfigurationFactory extends FileConfigurationFactory
{
/**
* Creates a new instance of <code>PropertiesConfigurationFactory</code>.
*/
public PropertiesConfigurationFactory()
{
super(null);
}
/**
* Creates the new configuration object. Based on the file name
* provided in the attributes either a <code>PropertiesConfiguration</code>
* or a <code>XMLPropertiesConfiguration</code> object will be
* returned.
*
* @param attributes the attributes
* @return the new configuration object
* @throws Exception if an error occurs
*/
protected FileConfiguration createConfiguration(Attributes attributes) throws Exception
{
String filename = attributes.getValue(ATTR_FILENAME);
if (filename != null && filename.toLowerCase().trim().endsWith(".xml"))
{
return new XMLPropertiesConfiguration();
}
else
{
return new PropertiesConfiguration();
}
}
}
/**
* A factory that returns an XMLPropertyListConfiguration for .xml files
* and a PropertyListConfiguration for the others.
*
* @since 1.2
*/
public class PropertyListConfigurationFactory extends FileConfigurationFactory
{
/**
* Creates a new instance of <code>PropertyListConfigurationFactory</code>.
*/
public PropertyListConfigurationFactory()
{
super(null);
}
/**
* Creates the new configuration object. Based on the file name
* provided in the attributes either a <code>XMLPropertyListConfiguration</code>
* or a <code>PropertyListConfiguration</code> object will be
* returned.
*
* @param attributes the attributes
* @return the new configuration object
* @throws Exception if an error occurs
*/
protected FileConfiguration createConfiguration(Attributes attributes) throws Exception
{
String filename = attributes.getValue(ATTR_FILENAME);
if (filename != null && filename.toLowerCase().trim().endsWith(".xml"))
{
return new XMLPropertyListConfiguration();
}
else
{
return new PropertyListConfiguration();
}
}
}
/**
* A tiny inner class that allows the Configuration Factory to
* let the digester construct JNDIConfiguration objects.
*/
private class JNDIConfigurationFactory extends DigesterConfigurationFactory
{
/**
* Creates a new instance of <code>JNDIConfigurationFactory</code>.
*/
public JNDIConfigurationFactory()
{
super(JNDIConfiguration.class);
}
}
/**
* A tiny inner class that allows the Configuration Factory to
* let the digester construct SystemConfiguration objects.
*/
private class SystemConfigurationFactory extends DigesterConfigurationFactory
{
/**
* Creates a new instance of <code>SystemConfigurationFactory</code>.
*/
public SystemConfigurationFactory()
{
super(SystemConfiguration.class);
}
}
/**
* A simple data class that holds all information about a configuration
* from the <code>&lt;additional&gt;</code> section.
*/
public static class AdditionalConfigurationData
{
/** Stores the configuration object.*/
private Configuration configuration;
/** Stores the location of this configuration in the global tree.*/
private String at;
/**
* Returns the value of the <code>at</code> attribute.
*
* @return the at attribute
*/
public String getAt()
{
return at;
}
/**
* Sets the value of the <code>at</code> attribute.
*
* @param string the attribute value
*/
public void setAt(String string)
{
at = string;
}
/**
* Returns the configuration object.
*
* @return the configuration
*/
public Configuration getConfiguration()
{
return configuration;
}
/**
* Sets the configuration object. Note: Normally this method should be
* named <code>setConfiguration()</code>, but the name
* <code>addConfiguration()</code> is required by some of the digester
* rules.
*
* @param config the configuration to set
*/
public void addConfiguration(Configuration config)
{
configuration = config;
}
}
/**
* An internally used helper class for constructing the composite
* configuration object.
*/
public static class ConfigurationBuilder
{
/** Stores the composite configuration.*/
private CompositeConfiguration config;
/** Stores a collection with the configs from the additional section.*/
private Collection additionalConfigs;
/**
* Creates a new instance of <code>ConfigurationBuilder</code>.
*/
public ConfigurationBuilder()
{
config = new CompositeConfiguration();
additionalConfigs = new LinkedList();
}
/**
* Adds a new configuration to this object. This method is called by
* Digester.
*
* @param conf the configuration to be added
*/
public void addConfiguration(Configuration conf)
{
config.addConfiguration(conf);
}
/**
* Adds information about an additional configuration. This method is
* called by Digester.
*
* @param data the data about the additional configuration
*/
public void addAdditionalConfig(AdditionalConfigurationData data)
{
additionalConfigs.add(data);
}
/**
* Returns the final composite configuration.
*
* @return the final configuration object
*/
public CompositeConfiguration getConfiguration()
{
if (!additionalConfigs.isEmpty())
{
Configuration unionConfig = createAdditionalConfiguration(additionalConfigs);
if (unionConfig != null)
{
addConfiguration(unionConfig);
}
additionalConfigs.clear();
}
return config;
}
/**
* Creates a configuration object with the union of all properties
* defined in the <code>&lt;additional&gt;</code> section. This
* implementation returns a <code>HierarchicalConfiguration</code>
* object.
*
* @param configs a collection with
* <code>AdditionalConfigurationData</code> objects
* @return the union configuration (can be <b>null</b>)
*/
protected Configuration createAdditionalConfiguration(Collection configs)
{
HierarchicalConfiguration result = new HierarchicalConfiguration();
for (Iterator it = configs.iterator(); it.hasNext();)
{
AdditionalConfigurationData cdata =
(AdditionalConfigurationData) it.next();
result.addNodes(cdata.getAt(),
createRootNode(cdata).getChildren());
}
return result.isEmpty() ? null : result;
}
/**
* Creates a configuration root node for the specified configuration.
*
* @param cdata the configuration data object
* @return a root node for this configuration
*/
private HierarchicalConfiguration.Node createRootNode(AdditionalConfigurationData cdata)
{
if (cdata.getConfiguration() instanceof HierarchicalConfiguration)
{
// we can directly use this configuration's root node
return ((HierarchicalConfiguration) cdata.getConfiguration()).getRoot();
}
else
{
// transform configuration to a hierarchical root node
HierarchicalConfiguration hc = new HierarchicalConfiguration();
ConfigurationUtils.copy(cdata.getConfiguration(), hc);
return hc.getRoot();
}
}
}
/**
* A special implementation of Digester's <code>CallMethodRule</code> that
* is internally used for calling a file configuration's <code>load()</code>
* method. This class difers from its ancestor that it catches all occuring
* exceptions when the specified method is called. It then checks whether
* for the corresponding configuration the optional attribute is set. If
* this is the case, the exception will simply be ignored.
*
* @since 1.4
*/
private static class CallOptionalMethodRule extends CallMethodRule
{
/** A flag whether the optional attribute is set for this node. */
private boolean optional;
/**
* Creates a new instance of <code>CallOptionalMethodRule</code> and
* sets the name of the method to invoke.
*
* @param methodName the name of the method
*/
public CallOptionalMethodRule(String methodName)
{
super(methodName);
}
/**
* Checks if the optional attribute is set.
*
* @param attrs the attributes
* @throws Exception if an error occurs
*/
public void begin(Attributes attrs) throws Exception
{
optional = attrs.getValue(ATTR_OPTIONAL) != null
&& PropertyConverter.toBoolean(
attrs.getValue(ATTR_OPTIONAL)).booleanValue();
super.begin(attrs);
}
/**
* Calls the method. If the optional attribute was set, occurring
* exceptions will be ignored.
*
* @throws Exception if an error occurs
*/
public void end() throws Exception
{
try
{
super.end();
}
catch (Exception ex)
{
if (optional)
{
log.warn("Could not create optional configuration!", ex);
}
else
{
throw ex;
}
}
}
}
}