#region License
/*
* Copyright 2002-2005 the original author or authors.
*
* 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.
*/
#endregion
#region Imports
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using Common.Logging;
using Spring.Collections;
#endregion
namespace Spring.Objects.Factory.Config{
/// <summary>
/// Resolves placeholder values in one or more object definitions.
/// </summary>
/// <remarks>
/// <p>
/// The default placeholder syntax follows the NAnt style: <c>${...}</c>.
/// Instances of this class can be configured in the same way as any other
/// object in a Spring.NET container, and so custom placeholder prefix
/// and suffix values can be set via the <see cref="PlaceholderPrefix"/>
/// and <see cref="PlaceholderSuffix"/> properties.
/// </p>
/// <example>
/// <p>
/// The following example XML context definition defines an object that has
/// a number of placeholders. The placeholders can easily be distinguished
/// by the presence of the <c>${}</c> characters.
/// </p>
/// <code escaped="true">
/// <object id="connStringProvider"
/// type="MyNamespace.OracleConnectionStringProvider, MyAssembly">
/// <property name="dataSource" value="${database.datasource}"/>
/// <property name="userId" value="${database.userid}"/>
/// <property name="password" value="${database.password}"/>
/// <property name="integratedSecurity"
/// value="${database.integratedsecurity}"/>
/// </object>
/// </code>
/// <p>
/// The associated XML configuration file for the above example containing the
/// values for the placeholders would contain a snippet such as ..
/// </p>
/// <code escaped="true">
/// <name-values>
/// <add key="database.datasource" value="MyOracleDB"/>
/// <add key="database.userid" value="sa"/>
/// <add key="database.password" value="g0ly4dk1n"/>
/// <add key="database.integratedsecurity" value="true"/>
/// </name-values>
/// </code>
/// <p>
/// The preceding XML snippet listing the various property keys and their
/// associated values needs to be inserted into the .NET config file of
/// your application (or Web.config file for your ASP.NET web application,
/// as the case may be), like so...
/// </p>
/// <code escaped="true">
/// <name-values>
/// <add key="database.datasource" value="MyOracleDB"/>
/// <add key="database.userid" value="sa"/>
/// <add key="database.password" value="g0ly4dk1n"/>
/// <add key="database.integratedsecurity" value="true"/>
/// </name-values>
/// </code>
/// </example>
/// <p>
/// <see cref="Spring.Objects.Factory.Config.PropertyPlaceholderConfigurer"/>
/// checks simple property values, lists, dictionaries, sets, constructor
/// values, object type name, and object names in
/// runtime object references (
/// <see cref="Spring.Objects.Factory.Config.RuntimeObjectReference"/>).
/// Furthermore, placeholder values can also cross-reference other
/// placeholders, in the manner of the following example where the
/// <c>rootPath</c> property is cross-referenced by the <c>subPath</c>
/// property.
/// </p>
/// <example>
/// <code escaped="true">
/// <name-values>
/// <add key="rootPath" value="myrootdir"/>
/// <add key="subPath" value="${rootPath}/subdir"/>
/// </name-values>
/// </code>
/// </example>
/// <p>
/// In contrast to the
/// <see cref="Spring.Objects.Factory.Config.PropertyOverrideConfigurer"/>
/// class, this configurer only permits the replacement of explicit
/// placeholders in object definitions. Therefore, the original definition
/// cannot specify any default values for its object properties, and the
/// placeholder configuration file is expected to contain an entry for each
/// defined placeholder. That is, if an object definition contains a
/// placeholder <c>${foo}</c>, there should be an associated
/// <c><add key="foo" value="..."/></c> entry in the
/// referenced placeholder configuration file. Default property values
/// can be defined via the inherited
/// <see cref="Spring.Objects.Factory.Config.PropertyResourceConfigurer.Properties"/>
/// collection to overcome any perceived limitation of this feature.
/// </p>
/// <p>
/// If a configurer cannot resolve a placeholder, and the value of the
/// <see cref="Spring.Objects.Factory.Config.PropertyPlaceholderConfigurer.IgnoreUnresolvablePlaceholders"/>
/// property is currently set to <see langword="false"/>, an
/// <see cref="Spring.Objects.Factory.ObjectDefinitionStoreException"/>
/// will be thrown. If you want to resolve properties from multiple configuration
/// resources, simply specify multiple resources via the
/// <see cref="Spring.Objects.Factory.Config.PropertyResourceConfigurer.Locations"/>
/// property. Finally, please note that you can also define multiple
/// <see cref="Spring.Objects.Factory.Config.PropertyPlaceholderConfigurer"/>
/// instances, each with their own custom placeholder syntax.
/// </p>
/// </remarks>
/// <author>Juergen Hoeller</author>
/// <author>Simon White (.NET)</author>
/// <seealso cref="Spring.Objects.Factory.Config.PropertyResourceConfigurer"/>
/// <seealso cref="Spring.Objects.Factory.Config.PropertyOverrideConfigurer"/>
/// <seealso cref="Spring.Objects.Factory.Config.IObjectDefinition"/>
[Serializable]
public class PropertyPlaceholderConfigurer : PropertyResourceConfigurer
{
/// <summary>
/// The default placeholder prefix.
/// </summary>
public static readonly string DefaultPlaceholderPrefix = "${";
/// <summary>
/// The default placeholder suffix.
/// </summary>
public static readonly string DefaultPlaceholderSuffix = "}";
private readonly ILog logger;
private bool ignoreUnresolvablePlaceholders = false;
private string placeholderPrefix = DefaultPlaceholderPrefix;
private string placeholderSuffix = DefaultPlaceholderSuffix;
private EnvironmentVariableMode environmentVariableMode = EnvironmentVariableMode.Fallback;
/// <summary>
/// Initializes the new instance
/// </summary>
public PropertyPlaceholderConfigurer()
{
logger = LogManager.GetLogger(this.GetType());
}
#region Properties
/// <summary>
/// The placeholder prefix (the default is <c>${</c>).
/// </summary>
/// <seealso cref="DefaultPlaceholderPrefix"/>
public string PlaceholderPrefix
{
set { placeholderPrefix = value; }
}
/// <summary>
/// The placeholder suffix (the default is <c>}</c>)
/// </summary>
/// <seealso cref="DefaultPlaceholderSuffix"/>
public string PlaceholderSuffix
{
set { placeholderSuffix = value; }
}
/// <summary>
/// Indicates whether unresolved placeholders should be ignored.
/// </summary>
public bool IgnoreUnresolvablePlaceholders
{
get { return ignoreUnresolvablePlaceholders; }
set { ignoreUnresolvablePlaceholders = value; }
}
/// <summary>
/// Controls how environment variables will be used to
/// replace property placeholders.
/// </summary>
/// <remarks>
/// <p>
/// See the overview of the
/// <see cref="Spring.Objects.Factory.Config.EnvironmentVariableMode"/>
/// enumeration for the available options.
/// </p>
/// </remarks>
public EnvironmentVariableMode EnvironmentVariableMode
{
set { environmentVariableMode = value; }
}
#endregion
/// <summary>
/// Apply the given properties to the supplied
/// <see cref="Spring.Objects.Factory.Config.IConfigurableListableObjectFactory"/>.
/// </summary>
/// <param name="factory">
/// The <see cref="Spring.Objects.Factory.Config.IConfigurableListableObjectFactory"/>
/// used by the application context.
/// </param>
/// <param name="props">The properties to apply.</param>
/// <exception cref="Spring.Objects.ObjectsException">
/// If an error occured.
/// </exception>
protected override void ProcessProperties(IConfigurableListableObjectFactory factory, NameValueCollection props)
{
PlaceholderResolveHandlerAdapter resolveAdapter = new PlaceholderResolveHandlerAdapter(this, props);
ObjectDefinitionVisitor visitor = new ObjectDefinitionVisitor(new ObjectDefinitionVisitor.ResolveHandler(resolveAdapter.ParseAndResolveVariables));
string[] objectDefinitionNames = factory.GetObjectDefinitionNames();
for (int i = 0; i < objectDefinitionNames.Length; ++i)
{
string name = objectDefinitionNames[i];
IObjectDefinition definition = factory.GetObjectDefinition(name);
try
{
visitor.VisitObjectDefinition(definition);
}
catch (ObjectDefinitionStoreException ex)
{
throw new ObjectDefinitionStoreException(
definition.ResourceDescription, name, ex.Message);
}
}
}
/// <summary>
/// Parse values recursively to be able to resolve cross-references between
/// placeholder values.
/// </summary>
/// <param name="properties">
/// The map of constructor arguments / property values.
/// </param>
/// <param name="strVal">The string to be resolved.</param>
/// <param name="visitedPlaceholders">The placeholders that have already been visited
/// during the current resolution attempt (used to detect circular references
/// between placeholders). Only non-null if we're parsing a nested placeholder.</param>
/// <exception cref="Spring.Objects.ObjectsException">
/// If an error occurs.
/// </exception>
/// <returns>The resolved string.</returns>
public virtual string ParseString(
NameValueCollection properties, string strVal, ISet visitedPlaceholders)
{
int startIndex = strVal.IndexOf(placeholderPrefix);
while (startIndex != -1)
{
int endIndex = strVal.IndexOf(
placeholderSuffix, startIndex + placeholderPrefix.Length);
if (endIndex != -1)
{
int pos = startIndex + placeholderPrefix.Length;
string placeholder = strVal.Substring(pos, endIndex - pos);
if (visitedPlaceholders.Contains(placeholder))
{
throw new ObjectDefinitionStoreException(
string.Format(
CultureInfo.InvariantCulture,
"Circular placeholder reference '{0}' detected " +
"in property definitions [{1}].",
placeholder, properties));
}
visitedPlaceholders.Add(placeholder);
string resolvedValue = ResolvePlaceholder(placeholder, properties, environmentVariableMode);
if (resolvedValue != null)
{
resolvedValue = ParseString(properties, resolvedValue, visitedPlaceholders);
#region Instrumentation
if (logger.IsDebugEnabled)
{
logger.Debug(string.Format(
CultureInfo.InvariantCulture,
"Resolving placeholder '{0}' to '{1}'.", placeholder, resolvedValue));
}
#endregion
strVal = strVal.Substring(0, startIndex) + resolvedValue + strVal.Substring(endIndex + 1);
startIndex = strVal.IndexOf(placeholderPrefix, startIndex + resolvedValue.Length);
}
else if (ignoreUnresolvablePlaceholders)
{
// simply return the unprocessed value...
return strVal;
}
else
{
throw new ObjectDefinitionStoreException(string.Format(
CultureInfo.InvariantCulture,
"Could not resolve placeholder '{0}'.", placeholder));
}
visitedPlaceholders.Remove(placeholder);
}
else
{
startIndex = -1;
}
}
return strVal;
}
/// <summary>
/// Resolve the given placeholder using the given name value collection,
/// performing an environment variables check according to the given mode.
/// </summary>
/// <remarks>
/// <p>
/// The default implementation delegates to
/// <see cref="ResolvePlaceholder(string, NameValueCollection)"/>
/// before/afer the environment variable check. Subclasses can override
/// this for custom resolution strategies, including customized points
/// for the environment properties check.
/// </p>
/// </remarks>
/// <param name="placeholder">The placeholder to resolve</param>
/// <param name="props">
/// The merged name value collection of this configurer.
/// </param>
/// <param name="mode">The environment variable mode.</param>
/// <returns>
/// The resolved value or <see langword="null"/> if none.
/// </returns>
/// <seelso cref="Spring.Objects.Factory.Config.EnvironmentVariableMode"/>
protected virtual string ResolvePlaceholder(string placeholder,
NameValueCollection props,
EnvironmentVariableMode mode)
{
string propertyValue = null;
if (mode == Spring.Objects.Factory.Config.EnvironmentVariableMode.Override)
{
propertyValue = Environment.GetEnvironmentVariable(placeholder);
}
if (propertyValue == null)
{
propertyValue = ResolvePlaceholder(placeholder, props);
}
if (propertyValue == null
&& mode == Spring.Objects.Factory.Config.EnvironmentVariableMode.Fallback)
{
propertyValue = Environment.GetEnvironmentVariable(placeholder);
}
return propertyValue;
}
/// <summary>
/// Resolve the given placeholder using the given name value collection.
/// </summary>
/// <remarks>
/// <p>
/// This (the default) implementation simply looks up the value of the
/// supplied <paramref name="placeholder"/> key.
/// </p>
/// <p>
/// Subclasses can override this for customized placeholder-to-key
/// mappings or custom resolution strategies, possibly just using the
/// given name value collection as fallback.
/// </p>
/// </remarks>
/// <param name="placeholder">The placeholder to resolve.</param>
/// <param name="props">
/// The merged name value collection of this configurer.
/// </param>
/// <returns>The resolved value.</returns>
protected virtual string ResolvePlaceholder(
string placeholder, NameValueCollection props)
{
return props[placeholder];
}
#region Helper class
private class PlaceholderResolveHandlerAdapter
{
private readonly PropertyPlaceholderConfigurer ppc;
private readonly NameValueCollection props;
public PlaceholderResolveHandlerAdapter(PropertyPlaceholderConfigurer outerPPC, NameValueCollection props)
{
ppc = outerPPC;
this.props = props;
}
public string ParseAndResolveVariables(string name)
{
return ppc.ParseString(props, name, new HashedSet());
}
}
#endregion
}
}
|