From 18b7d1eedd486f46b5bee29ed72a1cc236198d0e Mon Sep 17 00:00:00 2001 From: eeichinger Date: Wed, 29 Jul 2009 20:59:47 +0000 Subject: [PATCH] SPRNET-1243 SPRNET-1167 --- .../Config/CommandLineArgsVariableSource.cs | 15 ++ .../Config/ConfigSectionVariableSource.cs | 15 ++ .../Config/ConnectionStringsVariableSource.cs | 23 ++- .../Config/DictionaryVariableSource.cs | 135 ++++++++++++++ .../Config/EnvironmentVariableSource.cs | 11 ++ .../Objects/Factory/Config/IVariableSource.cs | 10 +- .../Factory/Config/ObjectDefinitionVisitor.cs | 52 +++--- .../Config/PropertyFileVariableSource.cs | 15 ++ .../Config/PropertyPlaceholderConfigurer.cs | 43 +++-- .../Factory/Config/RegistryVariableSource.cs | 12 ++ .../Config/SpecialFolderVariableSource.cs | 11 ++ .../Factory/Config/VariableAccessor.cs | 6 +- .../Config/VariablePlaceholderConfigurer.cs | 169 +++++++++++------- .../Spring.Core/Spring.Core.2008.csproj | 1 + .../Spring.Core/Util/CollectionUtils.cs | 32 +++- .../Expressions/ExpressionEvaluatorTests.cs | 12 ++ .../Config/ObjectDefinitionVisitorTests.cs | 56 ++---- .../Factory/Config/VariableAccessorTests.cs | 18 +- .../VariablePlaceholderConfigurerTests.cs | 126 ++++++++++--- .../Util/CollectionUtilsTests.cs | 7 +- .../ConfigSectionSessionScopeSettingsTests.cs | 18 +- 21 files changed, 562 insertions(+), 225 deletions(-) create mode 100644 src/Spring/Spring.Core/Objects/Factory/Config/DictionaryVariableSource.cs diff --git a/src/Spring/Spring.Core/Objects/Factory/Config/CommandLineArgsVariableSource.cs b/src/Spring/Spring.Core/Objects/Factory/Config/CommandLineArgsVariableSource.cs index 1a9fd8c4..e720f8f1 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Config/CommandLineArgsVariableSource.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Config/CommandLineArgsVariableSource.cs @@ -87,6 +87,21 @@ namespace Spring.Objects.Factory.Config set { valueSeparator = value; } } + /// + /// Before requesting a variable resolution, a client should + /// ask, whether the source can resolve a particular variable name. + /// + /// the name of the variable to resolve + /// true if the variable can be resolved, false otherwise + public bool CanResolveVariable(string name) + { + if (arguments == null) + { + InitArguments(); + } + return arguments.Contains(name); + } + /// /// Resolves variable value for the specified variable name. /// diff --git a/src/Spring/Spring.Core/Objects/Factory/Config/ConfigSectionVariableSource.cs b/src/Spring/Spring.Core/Objects/Factory/Config/ConfigSectionVariableSource.cs index 75e584d2..02e44559 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Config/ConfigSectionVariableSource.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Config/ConfigSectionVariableSource.cs @@ -92,6 +92,21 @@ namespace Spring.Objects.Factory.Config set { sectionNames = new string[] { value }; } } + /// + /// Before requesting a variable resolution, a client should + /// ask, whether the source can resolve a particular variable name. + /// + /// the name of the variable to resolve + /// true if the variable can be resolved, false otherwise + public bool CanResolveVariable(string name) + { + if (variables == null) + { + InitVariables(); + } + return CollectionUtils.Contains(variables.AllKeys, name); + } + /// /// Resolves variable value for the specified variable name. /// diff --git a/src/Spring/Spring.Core/Objects/Factory/Config/ConnectionStringsVariableSource.cs b/src/Spring/Spring.Core/Objects/Factory/Config/ConnectionStringsVariableSource.cs index 6a79bc79..64c8e099 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Config/ConnectionStringsVariableSource.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Config/ConnectionStringsVariableSource.cs @@ -20,6 +20,7 @@ #endregion using System; +using System.Collections; using System.Collections.Specialized; using System.Configuration; using Spring.Util; @@ -53,8 +54,24 @@ namespace Spring.Objects.Factory.Config [Serializable] public class ConnectionStringsVariableSource : IVariableSource { - private NameValueCollection variables; + private Hashtable variables; + + /// + /// Before requesting a variable resolution, a client should + /// ask, whether the source can resolve a particular variable name. + /// + /// the name of the variable to resolve + /// true if the variable can be resolved, false otherwise + public bool CanResolveVariable(string name) + { + if (variables == null) + { + InitVariables(); + } + return variables.Contains(name); + } + /// /// Resolves variable value for the specified variable name. /// @@ -70,7 +87,7 @@ namespace Spring.Objects.Factory.Config { InitVariables(); } - return variables.Get(name); + return (string) variables[name]; } /// @@ -79,7 +96,7 @@ namespace Spring.Objects.Factory.Config /// private void InitVariables() { - variables = new NameValueCollection(); + variables = CollectionsUtil.CreateCaseInsensitiveHashtable(); ConnectionStringSettingsCollection settings = ConfigurationManager.ConnectionStrings; foreach (ConnectionStringSettings setting in settings) { diff --git a/src/Spring/Spring.Core/Objects/Factory/Config/DictionaryVariableSource.cs b/src/Spring/Spring.Core/Objects/Factory/Config/DictionaryVariableSource.cs new file mode 100644 index 00000000..d45dfd3b --- /dev/null +++ b/src/Spring/Spring.Core/Objects/Factory/Config/DictionaryVariableSource.cs @@ -0,0 +1,135 @@ +#region License + +/* + * Copyright 2002-2009 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 + +using System; +using System.Collections; +using System.Collections.Specialized; + +namespace Spring.Objects.Factory.Config +{ + /// + /// A very simple, hashtable-based implementation of + /// + /// Erich Eichinger + public class DictionaryVariableSource : IVariableSource + { + private readonly Hashtable variables; + + /// + /// Creates a new, empty variable source + /// + public DictionaryVariableSource() + :this(null, true) + { + } + + /// + /// Creates a new, empty and case-insensitive variable source + /// + public DictionaryVariableSource(bool ignoreCase) + :this(null, ignoreCase) + { + } + + /// + /// Create a new variable source from a list of paired string values. + /// + /// + /// + /// The example below shows, how the dictionary is filled with { 'key1', 'value1' }, { 'key2', 'value2' } pairs: + /// + /// new DictionaryVariableSource( new string[] { "key1", "value1", "key2", "value2" } ) + /// + /// + /// + /// the argument list containing pairs, or null + public DictionaryVariableSource(params string[] args) + :this(true) + { + if (args != null) + { + for (int i = 0; i < args.Length; i += 2) + { + Add(args[i], args[i + 1]); + } + } + } + + /// + /// Creates a new variable source, reading values from another dictionary + /// and converting them to strings if necessary + /// + public DictionaryVariableSource(IDictionary dictionary, bool ignoreCase) + { + if (ignoreCase) + { + variables = CollectionsUtil.CreateCaseInsensitiveHashtable(); + } + else + { + variables = new Hashtable(); + } + + if (dictionary != null) + { + foreach (DictionaryEntry entry in dictionary) + { + string key = "" + entry.Key; + string value = entry.Value != null ? "" + entry.Value : null; + + variables[key] = value; + } + } + } + + /// + /// Adds a key/value pair + /// + /// this dictionary. allows for fluent config + public DictionaryVariableSource Add(string key, string value) + { + variables.Add(key, value); + return this; + } + + /// + /// Before requesting a variable resolution, a client should + /// ask, whether the source can resolve a particular variable name. + /// + /// the name of the variable to resolve + /// true if the variable can be resolved, false otherwise + public bool CanResolveVariable(string name) + { + return variables.ContainsKey(name); + } + + /// + /// Performs a variable name lookup + /// + public string ResolveVariable(string name) + { + if (!variables.ContainsKey(name)) + { + throw new ArgumentException(string.Format("variable '{0}' cannot be resolved", name)); + } + return (string)variables[name]; + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Core/Objects/Factory/Config/EnvironmentVariableSource.cs b/src/Spring/Spring.Core/Objects/Factory/Config/EnvironmentVariableSource.cs index b94170a0..5891e39a 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Config/EnvironmentVariableSource.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Config/EnvironmentVariableSource.cs @@ -30,6 +30,17 @@ namespace Spring.Objects.Factory.Config [Serializable] public class EnvironmentVariableSource : IVariableSource { + /// + /// Before requesting a variable resolution, a client should + /// ask, whether the source can resolve a particular variable name. + /// + /// the name of the variable to resolve + /// true if the variable can be resolved, false otherwise + public bool CanResolveVariable(string name) + { + return (Environment.GetEnvironmentVariable(name) != null); + } + /// /// Resolves variable value for the specified variable name. /// diff --git a/src/Spring/Spring.Core/Objects/Factory/Config/IVariableSource.cs b/src/Spring/Spring.Core/Objects/Factory/Config/IVariableSource.cs index 4fe30385..e96d9b8d 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Config/IVariableSource.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Config/IVariableSource.cs @@ -34,7 +34,7 @@ namespace Spring.Objects.Factory.Config ///

/// Users can always write their own variable sources implementations, /// that will allow them to load variable values from the database or - /// other proprietary data source.

+ /// other proprietary data source.

/// /// /// @@ -45,6 +45,14 @@ namespace Spring.Objects.Factory.Config /// Aleksandar Seovic public interface IVariableSource { + /// + /// Before requesting a variable resolution, a client should + /// ask, whether the source can resolve a particular variable name. + /// + /// the name of the variable to resolve + /// true if the variable can be resolved, false otherwise + bool CanResolveVariable(string name); + /// /// Resolves variable value for the specified variable name. /// diff --git a/src/Spring/Spring.Core/Objects/Factory/Config/ObjectDefinitionVisitor.cs b/src/Spring/Spring.Core/Objects/Factory/Config/ObjectDefinitionVisitor.cs index 53ceb048..7c50ddc2 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Config/ObjectDefinitionVisitor.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Config/ObjectDefinitionVisitor.cs @@ -44,17 +44,18 @@ namespace Spring.Objects.Factory.Config /// Mark Pollack public class ObjectDefinitionVisitor { - private IVariableSource variableSource; + public delegate string ResolveHandler(string rawStringValue); + private readonly ResolveHandler resolveHandler; /// /// Initializes a new instance of the class, /// applying the specified IVariableSource to all object metadata values. /// - /// The variable source. - public ObjectDefinitionVisitor(IVariableSource variableSource) + /// The handler to be called for resolving variables contained in a string. + public ObjectDefinitionVisitor(ResolveHandler resolveHandler) { - this.variableSource = variableSource; + this.resolveHandler = resolveHandler; } /// @@ -95,7 +96,7 @@ namespace Spring.Objects.Factory.Config string objectTypeName = objectDefinition.ObjectTypeName; if (objectTypeName != null) { - string resolvedName = ResolveStringValue(objectTypeName).ToString(); + string resolvedName = ResolveStringValue(objectTypeName); if (!objectTypeName.Equals(resolvedName)) { objectDefinition.ObjectTypeName = resolvedName; @@ -197,7 +198,7 @@ namespace Spring.Objects.Factory.Config { RuntimeObjectReference ror = (RuntimeObjectReference)value; //name has to be of string type. - string newObjectName = ResolveStringValue(ror.ObjectName).ToString(); + string newObjectName = ResolveStringValue(ror.ObjectName); if (!newObjectName.Equals(ror.ObjectName)) { return new RuntimeObjectReference(newObjectName); @@ -225,7 +226,7 @@ namespace Spring.Objects.Factory.Config String stringValue = typedStringValue.Value; if (stringValue != null) { - String visitedString = ResolveStringValue(stringValue).ToString(); + String visitedString = ResolveStringValue(stringValue); typedStringValue.Value = visitedString; } } @@ -236,7 +237,7 @@ namespace Spring.Objects.Factory.Config else if (value is ExpressionHolder) { ExpressionHolder holder = (ExpressionHolder)value; - string newExpressionString = ResolveStringValue(holder.ExpressionString).ToString(); + string newExpressionString = ResolveStringValue(holder.ExpressionString); return new ExpressionHolder(newExpressionString); } return value; @@ -251,7 +252,7 @@ namespace Spring.Objects.Factory.Config string elementTypeName = listVal.ElementTypeName; if (elementTypeName != null) { - string resolvedName = ResolveStringValue(elementTypeName).ToString(); + string resolvedName = ResolveStringValue(elementTypeName); if (!elementTypeName.Equals(resolvedName)) { listVal.ElementTypeName = resolvedName; @@ -278,7 +279,7 @@ namespace Spring.Objects.Factory.Config string elementTypeName = setVal.ElementTypeName; if (elementTypeName != null) { - string resolvedName = ResolveStringValue(elementTypeName).ToString(); + string resolvedName = ResolveStringValue(elementTypeName); if (!elementTypeName.Equals(resolvedName)) { setVal.ElementTypeName = resolvedName; @@ -306,7 +307,7 @@ namespace Spring.Objects.Factory.Config string keyTypeName = dictVal.KeyTypeName; if (keyTypeName != null) { - string resolvedName = ResolveStringValue(keyTypeName).ToString(); + string resolvedName = ResolveStringValue(keyTypeName); if (!keyTypeName.Equals(resolvedName)) { dictVal.KeyTypeName = resolvedName; @@ -316,7 +317,7 @@ namespace Spring.Objects.Factory.Config string valueTypeName = dictVal.ValueTypeName; if (valueTypeName != null) { - string resolvedName = ResolveStringValue(valueTypeName).ToString(); + string resolvedName = ResolveStringValue(valueTypeName); if (!valueTypeName.Equals(resolvedName)) { dictVal.ValueTypeName = resolvedName; @@ -357,23 +358,20 @@ namespace Spring.Objects.Factory.Config } /// - /// Looks up the value of the given variable name in the configured . - /// - /// The name of the variable to be looked up - /// - /// The value of this variable, as returned from the passed - /// into the constructor - /// - /// If no has been configured. - protected virtual object ResolveStringValue(string variableName) + /// calls the to resolve any variables contained in the raw string. + /// + /// the raw string value containing variable placeholders to be resolved + /// If no has been configured. + /// the resolved string, having variables being replaced, if any + protected virtual string ResolveStringValue(string rawStringValue) { - if (variableSource == null) + if (resolveHandler == null) { - throw new InvalidOperationException("No IVariableSource specified - pass an instance " + - "of this object into the constructor or override the 'ResolveStringValue' method"); - } - return variableSource.ResolveVariable(variableName); + throw new InvalidOperationException("No resolveHandler specified - pass an instance " + + "into the constructor or override the 'ResolveStringValue' method"); + } + + return resolveHandler(rawStringValue); } - } } \ No newline at end of file diff --git a/src/Spring/Spring.Core/Objects/Factory/Config/PropertyFileVariableSource.cs b/src/Spring/Spring.Core/Objects/Factory/Config/PropertyFileVariableSource.cs index 69fdce31..e4267ed5 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Config/PropertyFileVariableSource.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Config/PropertyFileVariableSource.cs @@ -64,6 +64,21 @@ namespace Spring.Objects.Factory.Config set { locations = new IResource[] { value} ;} } + /// + /// Before requesting a variable resolution, a client should + /// ask, whether the source can resolve a particular variable name. + /// + /// the name of the variable to resolve + /// true if the variable can be resolved, false otherwise + public bool CanResolveVariable(string name) + { + if (properties == null) + { + InitProperties(); + } + return properties.Contains(name); + } + /// /// Resolves variable value for the specified variable name. /// diff --git a/src/Spring/Spring.Core/Objects/Factory/Config/PropertyPlaceholderConfigurer.cs b/src/Spring/Spring.Core/Objects/Factory/Config/PropertyPlaceholderConfigurer.cs index 9256ca3d..c5656826 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Config/PropertyPlaceholderConfigurer.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Config/PropertyPlaceholderConfigurer.cs @@ -225,11 +225,10 @@ namespace Spring.Objects.Factory.Config /// /// If an error occured. /// - protected override void ProcessProperties( - IConfigurableListableObjectFactory factory, NameValueCollection props) - { - IVariableSource variableSource = new PlaceholderResolvingStringVariableSource(this, props); - ObjectDefinitionVisitor visitor = new ObjectDefinitionVisitor(variableSource); + 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) @@ -397,26 +396,26 @@ namespace Spring.Objects.Factory.Config { return props[placeholder]; } - } - #region Helper class + #region Helper class - internal class PlaceholderResolvingStringVariableSource : IVariableSource - { - private PropertyPlaceholderConfigurer ppc; - private NameValueCollection props; - - public PlaceholderResolvingStringVariableSource(PropertyPlaceholderConfigurer outerPPC, NameValueCollection props) + private class PlaceholderResolveHandlerAdapter { - ppc = outerPPC; - this.props = props; + 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()); + } } - public string ResolveVariable(string name) - { - return ppc.ParseString(props, name, new HashedSet()); - } - } - - #endregion + #endregion + } } \ No newline at end of file diff --git a/src/Spring/Spring.Core/Objects/Factory/Config/RegistryVariableSource.cs b/src/Spring/Spring.Core/Objects/Factory/Config/RegistryVariableSource.cs index 362f5a51..05c21b8b 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Config/RegistryVariableSource.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Config/RegistryVariableSource.cs @@ -30,6 +30,7 @@ namespace Spring.Objects.Factory.Config /// Aleksandar Seovic public class RegistryVariableSource : IVariableSource { + private static readonly object NULL = new object(); private RegistryKey key; /// @@ -44,6 +45,17 @@ namespace Spring.Objects.Factory.Config set { key = value; } } + /// + /// Before requesting a variable resolution, a client should + /// ask, whether the source can resolve a particular variable name. + /// + /// the name of the variable to resolve + /// true if the variable can be resolved, false otherwise + public bool CanResolveVariable(string name) + { + return (key != null && key.GetValue(name, NULL) != NULL); + } + /// /// Resolves variable value for the specified variable name. /// diff --git a/src/Spring/Spring.Core/Objects/Factory/Config/SpecialFolderVariableSource.cs b/src/Spring/Spring.Core/Objects/Factory/Config/SpecialFolderVariableSource.cs index 0048f39f..2ab74412 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Config/SpecialFolderVariableSource.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Config/SpecialFolderVariableSource.cs @@ -31,6 +31,17 @@ namespace Spring.Objects.Factory.Config [Serializable] public class SpecialFolderVariableSource : IVariableSource { + /// + /// Before requesting a variable resolution, a client should + /// ask, whether the source can resolve a particular variable name. + /// + /// the name of the variable to resolve + /// true if the variable can be resolved, false otherwise + public bool CanResolveVariable(string name) + { + return ResolveVariable(name) != null; + } + /// /// Resolves specified special folder to its full path. /// diff --git a/src/Spring/Spring.Core/Objects/Factory/Config/VariableAccessor.cs b/src/Spring/Spring.Core/Objects/Factory/Config/VariableAccessor.cs index 2fa7158d..bacc5174 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Config/VariableAccessor.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Config/VariableAccessor.cs @@ -774,7 +774,11 @@ namespace Spring.Objects.Factory.Config /// public string GetString(string name, string defaultValue) { - string value = (variableSource == null) ? defaultValue : variableSource.ResolveVariable(name); + string value = null; + if (variableSource != null && variableSource.CanResolveVariable(name)) + { + value = variableSource.ResolveVariable(name); + } if (!StringUtils.HasLength(value)) { diff --git a/src/Spring/Spring.Core/Objects/Factory/Config/VariablePlaceholderConfigurer.cs b/src/Spring/Spring.Core/Objects/Factory/Config/VariablePlaceholderConfigurer.cs index 7ac85308..a4e75065 100644 --- a/src/Spring/Spring.Core/Objects/Factory/Config/VariablePlaceholderConfigurer.cs +++ b/src/Spring/Spring.Core/Objects/Factory/Config/VariablePlaceholderConfigurer.cs @@ -24,6 +24,7 @@ using System.Globalization; using Common.Logging; using Spring.Collections; using Spring.Core; +using Spring.Util; namespace Spring.Objects.Factory.Config { @@ -66,12 +67,12 @@ namespace Spring.Objects.Factory.Config /// /// The default placeholder prefix. /// - public const string DefaultPlaceholderPrefix = "${"; + public static readonly string DefaultPlaceholderPrefix = "${"; /// /// The default placeholder suffix. /// - public const string DefaultPlaceholderSuffix = "}"; + public static readonly string DefaultPlaceholderSuffix = "}"; #region Fields @@ -81,10 +82,33 @@ namespace Spring.Objects.Factory.Config private string placeholderPrefix = DefaultPlaceholderPrefix; private string placeholderSuffix = DefaultPlaceholderSuffix; - private IList variableSourceList; + private IList variableSourceList = new ArrayList(); #endregion + /// + /// Create a new instance without any variable sources + /// + public VariablePlaceholderConfigurer() + {} + + /// + /// Create a new instance and initialize with the given variable source + /// + /// + public VariablePlaceholderConfigurer(IVariableSource variableSource) + { + this.VariableSource = variableSource; + } + + /// + /// Create a new instance and initialize with the given list of variable sources + /// + public VariablePlaceholderConfigurer(IList variableSources) + { + this.VariableSources = variableSources; + } + #region Properties /// @@ -156,6 +180,17 @@ namespace Spring.Objects.Factory.Config /// public void PostProcessObjectFactory( IConfigurableListableObjectFactory factory ) { + if (CollectionUtils.IsEmpty(variableSourceList)) + { + throw new ArgumentException("No VariableSources configured"); + } + + ICollection filtered = CollectionUtils.FindValuesOfType(this.variableSourceList, typeof (IVariableSource)); + if (filtered.Count != this.variableSourceList.Count) + { + throw new ArgumentException("'VariableSources' must contain IVariableSource elements only", "VariableSources"); + } + try { ProcessProperties( factory ); @@ -206,11 +241,9 @@ namespace Spring.Objects.Factory.Config /// protected virtual void ProcessProperties( IConfigurableListableObjectFactory factory ) { - IVariableSource compositeVariableSource = new PlaceholderResolvingCompositeVariableSource( placeholderPrefix - , placeholderSuffix - , variableSourceList - , ignoreUnresolvablePlaceholders ); - ObjectDefinitionVisitor visitor = new ObjectDefinitionVisitor( compositeVariableSource ); + CompositeVariableSource compositeVariableSource = new CompositeVariableSource(variableSourceList); + TextProcessor tp = new TextProcessor(this, compositeVariableSource); + ObjectDefinitionVisitor visitor = new ObjectDefinitionVisitor(new ObjectDefinitionVisitor.ResolveHandler(tp.ParseAndResolveVariables)); string[] objectDefinitionNames = factory.GetObjectDefinitionNames(); for (int i = 0; i < objectDefinitionNames.Length; ++i) @@ -231,84 +264,86 @@ namespace Spring.Objects.Factory.Config #region Helper class - private class PlaceholderResolvingCompositeVariableSource : IVariableSource + private class TextProcessor { - private readonly ILog logger = LogManager.GetLogger( typeof( PlaceholderResolvingCompositeVariableSource ) ); + private readonly ILog logger = LogManager.GetLogger(typeof(TextProcessor)); + private readonly VariablePlaceholderConfigurer owner; + private readonly IVariableSource variableSource; - private readonly string placeholderPrefix; - private readonly string placeholderSuffix; - private readonly bool ignoreUnresolvablePlaceholders; - private readonly IList variableSourceList; - - public PlaceholderResolvingCompositeVariableSource( string placeholderPrefix, string placeholderSuffix, IList variableSourceList, bool ignoreUnresolvablePlaceholders ) + public TextProcessor(VariablePlaceholderConfigurer owner, IVariableSource variableSource) { - this.placeholderPrefix = placeholderPrefix; - this.placeholderSuffix = placeholderSuffix; - this.variableSourceList = variableSourceList; - this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders; + this.owner = owner; + this.variableSource = variableSource; } - #region IVariableSource Members - - public string ResolveVariable( string rawStringValue ) + public string ParseAndResolveVariables(string rawStringValue) { - return ParseAndResolveVariable( rawStringValue, new HashedSet() ); + return ParseAndResolveVariables(rawStringValue, new HashedSet()); } - - //TODO handle resolved values at are not string - identify this case as only 1 placeholder present? - - private string ParseAndResolveVariable( string strVal, ISet visitedPlaceholders ) + private string ParseAndResolveVariables(string strVal, ISet visitedPlaceholders) { - int startIndex = strVal.IndexOf( placeholderPrefix ); + if (strVal == null) + { + return null; + } + + int startIndex = strVal.IndexOf(owner.placeholderPrefix); while (startIndex != -1) { int endIndex = strVal.IndexOf( - placeholderSuffix, startIndex + placeholderPrefix.Length ); + owner.placeholderSuffix, startIndex + owner.placeholderPrefix.Length); if (endIndex != -1) { - int pos = startIndex + placeholderPrefix.Length; - string placeholder = strVal.Substring( pos, endIndex - pos ); - if (visitedPlaceholders.Contains( placeholder )) + int pos = startIndex + owner.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. ", - placeholder ) ); + placeholder)); } - visitedPlaceholders.Add( placeholder ); - string resolvedValue = ResolvePlaceholderVariable( placeholder ); - if (resolvedValue != null) + visitedPlaceholders.Add(placeholder); + + if (variableSource.CanResolveVariable(placeholder)) { - resolvedValue = ParseAndResolveVariable( resolvedValue, visitedPlaceholders ); + string resolvedValue = variableSource.ResolveVariable(placeholder); + resolvedValue = ParseAndResolveVariables(resolvedValue, visitedPlaceholders); #region Instrumentation if (logger.IsDebugEnabled) { - logger.Debug( string.Format( + logger.Debug(string.Format( CultureInfo.InvariantCulture, - "Resolving placeholder '{0}' to '{1}'.", placeholder, resolvedValue ) ); + "Resolving placeholder '{0}' to '{1}'.", placeholder, resolvedValue)); } #endregion - strVal = strVal.Substring( 0, startIndex ) + resolvedValue + strVal.Substring( endIndex + placeholderSuffix.Length ); - startIndex = strVal.IndexOf( placeholderPrefix, startIndex + resolvedValue.Length); + if (resolvedValue == null + && startIndex == 0 + && strVal.Length <= endIndex + owner.placeholderSuffix.Length) + { + return null; + } + strVal = strVal.Substring(0, startIndex) + resolvedValue + strVal.Substring(endIndex + owner.placeholderSuffix.Length); + startIndex = strVal.IndexOf(owner.placeholderPrefix, startIndex + (resolvedValue == null ? 0 : resolvedValue.Length)); } - else if (ignoreUnresolvablePlaceholders) + else if (owner.ignoreUnresolvablePlaceholders) { // simply return the unprocessed value... return strVal; } else { - throw new ObjectDefinitionStoreException( string.Format( + throw new ObjectDefinitionStoreException(string.Format( CultureInfo.InvariantCulture, - "Could not resolve placeholder '{0}'.", placeholder ) ); + "Could not resolve placeholder '{0}'.", placeholder)); } - visitedPlaceholders.Remove( placeholder ); + visitedPlaceholders.Remove(placeholder); } else { @@ -316,34 +351,38 @@ namespace Spring.Objects.Factory.Config } } return strVal; + } + } + + private class CompositeVariableSource : IVariableSource + { + private readonly IList variableSourceList; + + public CompositeVariableSource( IList variableSourceList ) + { + this.variableSourceList = variableSourceList; } - private string ResolvePlaceholderVariable( string variableName ) + public string ResolveVariable( string variableName ) { foreach (IVariableSource variableSource in variableSourceList) { - //TODO handle resolved values at are not strings? + if (!variableSource.CanResolveVariable(variableName)) continue; - object resolvedValue = variableSource.ResolveVariable( variableName ); - if (resolvedValue is string) - { - } - if (resolvedValue != null) - { - if (resolvedValue is string) - { - return resolvedValue as string; - } - else - { - logger.Warn( "Placeholder " + variableSource + " resolved to object type [" + resolvedValue.GetType() + "]. Only string type currently supported" ); - } - } + return variableSource.ResolveVariable( variableName ); } - return null; + throw new ArgumentException(string.Format("cannot resolve variable '{0}'", variableName)); } - #endregion + public bool CanResolveVariable(string variableName) + { + foreach (IVariableSource variableSource in variableSourceList) + { + if (variableSource.CanResolveVariable(variableName)) + return true; + } + return false; + } } #endregion diff --git a/src/Spring/Spring.Core/Spring.Core.2008.csproj b/src/Spring/Spring.Core/Spring.Core.2008.csproj index a70fb52a..0a49fa7c 100644 --- a/src/Spring/Spring.Core/Spring.Core.2008.csproj +++ b/src/Spring/Spring.Core/Spring.Core.2008.csproj @@ -666,6 +666,7 @@ + diff --git a/src/Spring/Spring.Core/Util/CollectionUtils.cs b/src/Spring/Spring.Core/Util/CollectionUtils.cs index 10e064d2..94fb820e 100644 --- a/src/Spring/Spring.Core/Util/CollectionUtils.cs +++ b/src/Spring/Spring.Core/Util/CollectionUtils.cs @@ -70,20 +70,36 @@ namespace Spring.Util /// The collection to check. /// The object to locate in the collection. /// if the element is in the collection, otherwise. - public static bool Contains(ICollection collection, Object element) + public static bool Contains(IEnumerable collection, Object element) { - // TODO (EE): does not match Spring/J behavior. Change to IEnumerable and enumerable may be null if (collection == null) { - throw new ArgumentNullException("collection", "Collection cannot be null."); + return false; } - MethodInfo method; - method = collection.GetType().GetMethod("contains", BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public); - if (null == method) + + if (collection is IList) { - throw new InvalidOperationException("Collection type " + collection.GetType() + " does not implement a Contains() method."); + return ((IList) collection).Contains(element); } - return (bool)method.Invoke(collection, new Object[] { element }); + + if (collection is IDictionary) + { + return ((IDictionary) collection).Contains(element); + } + + MethodInfo method = collection.GetType().GetMethod("contains", BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public); + if (null != method) + { + return (bool)method.Invoke(collection, new Object[] { element }); + } + foreach (object item in collection) + { + if (object.Equals(item, element)) + { + return true; + } + } + return false; } /// diff --git a/test/Spring/Spring.Core.Tests/Expressions/ExpressionEvaluatorTests.cs b/test/Spring/Spring.Core.Tests/Expressions/ExpressionEvaluatorTests.cs index b0a0c73d..db8af40f 100644 --- a/test/Spring/Spring.Core.Tests/Expressions/ExpressionEvaluatorTests.cs +++ b/test/Spring/Spring.Core.Tests/Expressions/ExpressionEvaluatorTests.cs @@ -723,6 +723,18 @@ namespace Spring.Expressions Assert.IsTrue((bool)ExpressionEvaluator.GetValue(tesla, "T(System.DateTime) == DOB.GetType()")); } + /// + /// Tests type node + /// + [Test] + public void TestTypeNodeWithArrays() + { + Assert.AreEqual(typeof(DateTime[]), ExpressionEvaluator.GetValue(null, "T(System.DateTime[])")); + Assert.AreEqual(typeof(DateTime[,]), ExpressionEvaluator.GetValue(null, "T(System.DateTime[,])")); + Assert.AreEqual(typeof(DateTime[]), ExpressionEvaluator.GetValue(null, "T(System.DateTime[], mscorlib)")); + Assert.AreEqual(typeof(DateTime[,]), ExpressionEvaluator.GetValue(null, "T(System.DateTime[,], mscorlib)")); + } + /// /// Tests type node /// diff --git a/test/Spring/Spring.Core.Tests/Objects/Factory/Config/ObjectDefinitionVisitorTests.cs b/test/Spring/Spring.Core.Tests/Objects/Factory/Config/ObjectDefinitionVisitorTests.cs index 2e441657..34b8c7e8 100644 --- a/test/Spring/Spring.Core.Tests/Objects/Factory/Config/ObjectDefinitionVisitorTests.cs +++ b/test/Spring/Spring.Core.Tests/Objects/Factory/Config/ObjectDefinitionVisitorTests.cs @@ -18,10 +18,8 @@ #endregion -using System; using System.Collections; using System.Collections.Specialized; - using NUnit.Framework; using Spring.Objects.Factory.Support; @@ -34,14 +32,22 @@ namespace Spring.Objects.Factory.Config [TestFixture] public class ObjectDefinitionVisitorTests { - private NameValueCollectionVariableSource variableSource; + private Hashtable properties; [SetUp] public void SetUp() { - NameValueCollection nvc = new NameValueCollection(); - nvc.Add("Property", "Value"); - variableSource = new NameValueCollectionVariableSource(nvc); + properties = CollectionsUtil.CreateCaseInsensitiveHashtable(); + properties.Add("Property", "Value"); + } + + private string ParseAndResolveVariables(string rawText) + { + if (rawText.StartsWith("$")) + { + return (string) properties[rawText.Substring(1)]; + } + return rawText; } [Test] @@ -50,7 +56,7 @@ namespace Spring.Objects.Factory.Config IObjectDefinition od = new RootObjectDefinition(); od.ObjectTypeName = "$Property"; - ObjectDefinitionVisitor odv = new ObjectDefinitionVisitor(variableSource); + ObjectDefinitionVisitor odv = new ObjectDefinitionVisitor(new ObjectDefinitionVisitor.ResolveHandler(ParseAndResolveVariables)); odv.VisitObjectDefinition(od); Assert.AreEqual("Value", od.ObjectTypeName); @@ -62,7 +68,7 @@ namespace Spring.Objects.Factory.Config IObjectDefinition od = new RootObjectDefinition(); od.PropertyValues.Add("PropertyName", "$Property"); - ObjectDefinitionVisitor odv = new ObjectDefinitionVisitor(variableSource); + ObjectDefinitionVisitor odv = new ObjectDefinitionVisitor(new ObjectDefinitionVisitor.ResolveHandler(ParseAndResolveVariables)); odv.VisitObjectDefinition(od); Assert.AreEqual("Value", od.PropertyValues.GetPropertyValue("PropertyName").Value); @@ -78,7 +84,7 @@ namespace Spring.Objects.Factory.Config ml.Add("$Property"); od.PropertyValues.Add("PropertyName", ml); - ObjectDefinitionVisitor odv = new ObjectDefinitionVisitor(variableSource); + ObjectDefinitionVisitor odv = new ObjectDefinitionVisitor(new ObjectDefinitionVisitor.ResolveHandler(ParseAndResolveVariables)); odv.VisitObjectDefinition(od); ManagedList list = od.PropertyValues.GetPropertyValue("PropertyName").Value as ManagedList; @@ -96,7 +102,7 @@ namespace Spring.Objects.Factory.Config ms.Add("$Property"); od.PropertyValues.Add("PropertyName", ms); - ObjectDefinitionVisitor odv = new ObjectDefinitionVisitor(variableSource); + ObjectDefinitionVisitor odv = new ObjectDefinitionVisitor(new ObjectDefinitionVisitor.ResolveHandler(ParseAndResolveVariables)); odv.VisitObjectDefinition(od); ManagedSet set = od.PropertyValues.GetPropertyValue("PropertyName").Value as ManagedSet; @@ -117,7 +123,7 @@ namespace Spring.Objects.Factory.Config md.Add("Key", "$Property"); od.PropertyValues.Add("PropertyName", md); - ObjectDefinitionVisitor odv = new ObjectDefinitionVisitor(variableSource); + ObjectDefinitionVisitor odv = new ObjectDefinitionVisitor(new ObjectDefinitionVisitor.ResolveHandler(ParseAndResolveVariables)); odv.VisitObjectDefinition(od); ManagedDictionary dictionary = od.PropertyValues.GetPropertyValue("PropertyName").Value as ManagedDictionary; @@ -135,7 +141,7 @@ namespace Spring.Objects.Factory.Config nvc["Key"] = "$Property"; od.PropertyValues.Add("PropertyName", nvc); - ObjectDefinitionVisitor odv = new ObjectDefinitionVisitor(variableSource); + ObjectDefinitionVisitor odv = new ObjectDefinitionVisitor(new ObjectDefinitionVisitor.ResolveHandler(ParseAndResolveVariables)); odv.VisitObjectDefinition(od); NameValueCollection visitedNvc = @@ -143,31 +149,5 @@ namespace Spring.Objects.Factory.Config Assert.AreEqual("Value", visitedNvc["Key"]); } - - #region Helper class - - public class NameValueCollectionVariableSource : IVariableSource - { - private NameValueCollection properties; - - public NameValueCollectionVariableSource(NameValueCollection properties) - { - this.properties = properties; - } - - public string ResolveVariable(string name) - { - if (name.StartsWith("$")) - { - return properties[name.Substring(1)]; - } - else - { - return name; - } - } - } - - #endregion } } \ No newline at end of file diff --git a/test/Spring/Spring.Core.Tests/Objects/Factory/Config/VariableAccessorTests.cs b/test/Spring/Spring.Core.Tests/Objects/Factory/Config/VariableAccessorTests.cs index 92d7208f..31939fa9 100644 --- a/test/Spring/Spring.Core.Tests/Objects/Factory/Config/VariableAccessorTests.cs +++ b/test/Spring/Spring.Core.Tests/Objects/Factory/Config/VariableAccessorTests.cs @@ -41,23 +41,7 @@ namespace Spring.Objects.Factory.Config private static readonly DateTime TESTDATETIME = new DateTime(2007, 07, 06, 11, 12, 13); private static readonly DateTime TESTDATETIME_DEFAULT = TESTDATETIME.AddDays(-1); - private class NameValueCollectionVariableSource : IVariableSource - { - private readonly NameValueCollection nameValues = new NameValueCollection(); - - public NameValueCollectionVariableSource Add(string name, string value) - { - nameValues.Add(name, value); - return this; - } - - public string ResolveVariable(string name) - { - return nameValues.Get(name); - } - } - - private readonly IVariableSource _testVariableSource = new NameValueCollectionVariableSource() + private readonly IVariableSource _testVariableSource = new DictionaryVariableSource(null, true) .Add("ValidString", "String") .Add("EmptyString", "") .Add("ValidChar", "c") diff --git a/test/Spring/Spring.Core.Tests/Objects/Factory/Config/VariablePlaceholderConfigurerTests.cs b/test/Spring/Spring.Core.Tests/Objects/Factory/Config/VariablePlaceholderConfigurerTests.cs index cb334326..e453cfd5 100644 --- a/test/Spring/Spring.Core.Tests/Objects/Factory/Config/VariablePlaceholderConfigurerTests.cs +++ b/test/Spring/Spring.Core.Tests/Objects/Factory/Config/VariablePlaceholderConfigurerTests.cs @@ -1,7 +1,7 @@ #region License /* - * Copyright © 2002-2007 the original author or authors. + * Copyright © 2002-2009 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. @@ -18,45 +18,49 @@ #endregion -#region Imports - using System; using System.Collections; using NUnit.Framework; using Spring.Context.Support; - -#endregion +using Spring.Objects.Factory.Support; namespace Spring.Objects.Factory.Config { /// - /// This calss contains tests for + /// This class contains tests for /// /// Mark Pollack [TestFixture] public class VariablePlaceholderConfigurerTests { - private class DictionaryVariableSource : IVariableSource + [Test] + public void ThrowsOnMissingVariableSources() { - private Hashtable variables = new Hashtable(); - - public DictionaryVariableSource(params string[] args) + StaticApplicationContext ac = new StaticApplicationContext(); + VariablePlaceholderConfigurer vphc = new VariablePlaceholderConfigurer(); + try { - for(int i=0;i