From 4e006d8635395a37fd9cbc875bc8db14085d6a17 Mon Sep 17 00:00:00 2001 From: erezmazor Date: Sat, 1 Aug 2009 15:07:11 +0000 Subject: [PATCH] SPRNET-1237: Fix to parsing of velocity properties in custom namespace --- .../Config/TemplateNamespaceParser.cs | 150 ++++++++++++++++-- .../Config/TemplateNamespaceParserTests.cs | 22 ++- .../VelocityEngineFactoryObjectTests.xml | 27 ++-- 3 files changed, 169 insertions(+), 30 deletions(-) diff --git a/src/Spring/Spring.Template.Velocity/Template/Velocity/Config/TemplateNamespaceParser.cs b/src/Spring/Spring.Template.Velocity/Template/Velocity/Config/TemplateNamespaceParser.cs index c2003389..510fac7b 100644 --- a/src/Spring/Spring.Template.Velocity/Template/Velocity/Config/TemplateNamespaceParser.cs +++ b/src/Spring/Spring.Template.Velocity/Template/Velocity/Config/TemplateNamespaceParser.cs @@ -28,6 +28,7 @@ using NVelocity.Runtime; using NVelocity.Runtime.Resource.Loader; using Spring.Core.TypeResolution; using Spring.Objects.Factory; +using Spring.Objects.Factory.Config; using Spring.Objects.Factory.Support; using Spring.Objects.Factory.Xml; using Spring.Template.Velocity; @@ -273,7 +274,8 @@ namespace Spring.Template.Velocity.Config { /// the parser context /// the properties used to initialize the velocity engine private void ParseNVelocityProperties(XmlElement element, ParserContext parserContext, IDictionary properties) { - IDictionary parsedProperties = objectNamespaceParserHelper.ParseDictionaryElementInternal(element, "NVelocityProperties", parserContext); + IDictionary parsedProperties = objectNamespaceParserHelper.ParseDictionaryElementInternal(element, + TemplateDefinitionConstants.ElementNVelocityProperties, parserContext); foreach (DictionaryEntry entry in parsedProperties) { properties.Add(Convert.ToString(entry.Key), entry.Value); } @@ -287,17 +289,6 @@ namespace Spring.Template.Velocity.Config { return TemplateTypePrefix + element.LocalName; } - - /// - /// Helper class to access the ParseDictionaryElement of the ObjectsNamespaceParser - /// todo: remove this and externalize the logic in ObjectsNamespaceParser.ParseDictionaryElement - /// - internal sealed class ObjectNamespaceParserHelper : ObjectsNamespaceParser { - public IDictionary ParseDictionaryElementInternal(XmlElement element, string name, ParserContext parserContext) { - return ParseDictionaryElement(element, name, parserContext); - } - } - /// /// constructs an nvelocity style resource loader property in the format: /// prefix.resource.loader.suffix @@ -309,7 +300,139 @@ namespace Spring.Template.Velocity.Config { return type + VelocityConstants.Separator + RuntimeConstants.RESOURCE_LOADER + VelocityConstants.Separator + suffix; } - #region Element & Attribute Name Constants + } + + + + /// + /// Helper class to access the ParseDictionaryElement of the ObjectsNamespaceParser + /// todo: remove this and externalize the logic in ObjectsNamespaceParser.ParseDictionaryElement + /// + internal class ObjectNamespaceParserHelper : ObjectsNamespaceParser { + /// + /// Gets a dictionary definition. + /// + /// The element describing the dictionary definition. + /// The name of the object (definition) associated with the dictionary definition. + /// The namespace-aware parser. + /// The dictionary definition. + public IDictionary ParseDictionaryElementInternal(XmlElement mapEle, string name, ParserContext parserContext) { + ManagedDictionary dictionary = new ManagedDictionary(); + string keyTypeName = GetAttributeValue(mapEle, "key-type"); + string valueTypeName = GetAttributeValue(mapEle, "value-type"); + if (StringUtils.HasText(keyTypeName)) { + dictionary.KeyTypeName = keyTypeName; + } + if (StringUtils.HasText(valueTypeName)) { + dictionary.ValueTypeName = valueTypeName; + } + dictionary.MergeEnabled = ParseMergeAttribute(mapEle, parserContext.ParserHelper); + + XmlNodeList entryElements = SelectNodes(mapEle, ObjectDefinitionConstants.EntryElement); + foreach (XmlElement entryEle in entryElements) { + #region Key + + object key = null; + + XmlAttribute keyAtt = entryEle.Attributes[ObjectDefinitionConstants.KeyAttribute]; + if (keyAtt != null) { + key = keyAtt.Value; + } else { + // ok, we're not using the 'key' attribute; lets check for the ref shortcut... + XmlAttribute keyRefAtt = entryEle.Attributes[ObjectDefinitionConstants.DictionaryKeyRefShortcutAttribute]; + if (keyRefAtt != null) { + key = new RuntimeObjectReference(keyRefAtt.Value); + } else { + // so check for the 'key' element... + XmlNode keyNode = SelectSingleNode(entryEle, ObjectDefinitionConstants.KeyElement); + if (keyNode == null) { + throw new ObjectDefinitionStoreException( + parserContext.ReaderContext.Resource, name, + string.Format("One of either the '{0}' element, or the the '{1}' or '{2}' attributes " + + "is required for the <{3}/> element.", + ObjectDefinitionConstants.KeyElement, + ObjectDefinitionConstants.KeyAttribute, + ObjectDefinitionConstants.DictionaryKeyRefShortcutAttribute, + ObjectDefinitionConstants.EntryElement)); + } + XmlElement keyElement = (XmlElement)keyNode; + XmlNodeList keyNodes = keyElement.GetElementsByTagName("*"); + if (keyNodes == null || keyNodes.Count == 0) { + throw new ObjectDefinitionStoreException( + parserContext.ReaderContext.Resource, name, + string.Format("Malformed <{0}/> element... the value of the key must be " + + "specified as a child value-style element.", + ObjectDefinitionConstants.KeyElement)); + } + key = ParsePropertySubElement((XmlElement)keyNodes.Item(0), name, parserContext); + } + } + + #endregion + + #region Value + + XmlAttribute inlineValueAtt = entryEle.Attributes[ObjectDefinitionConstants.ValueAttribute]; + if (inlineValueAtt != null) { + // ok, we're using the value attribute shortcut... + dictionary[key] = inlineValueAtt.Value; + } else if (entryEle.Attributes[ObjectDefinitionConstants.DictionaryValueRefShortcutAttribute] != null) { + // ok, we're using the value-ref attribute shortcut... + XmlAttribute inlineValueRefAtt = entryEle.Attributes[ObjectDefinitionConstants.DictionaryValueRefShortcutAttribute]; + RuntimeObjectReference ror = new RuntimeObjectReference(inlineValueRefAtt.Value); + dictionary[key] = ror; + } else if (entryEle.Attributes[ObjectDefinitionConstants.ExpressionAttribute] != null) { + // ok, we're using the expression attribute shortcut... + XmlAttribute inlineExpressionAtt = entryEle.Attributes[ObjectDefinitionConstants.ExpressionAttribute]; + ExpressionHolder expHolder = new ExpressionHolder(inlineExpressionAtt.Value); + dictionary[key] = expHolder; + } else { + XmlNode keyNode = SelectSingleNode(entryEle, ObjectDefinitionConstants.KeyElement); + if (keyNode != null) { + entryEle.RemoveChild(keyNode); + } + // ok, we're using the original full-on value element... + XmlNodeList valueElements = entryEle.GetElementsByTagName("*"); + if (valueElements == null || valueElements.Count == 0) { + throw new ObjectDefinitionStoreException( + parserContext.ReaderContext.Resource, name, + string.Format("One of either the '{0}' or '{1}' attributes, or a value-style element " + + "is required for the <{2}/> element.", + ObjectDefinitionConstants.ValueAttribute, ObjectDefinitionConstants.DictionaryValueRefShortcutAttribute, ObjectDefinitionConstants.EntryElement)); + } + dictionary[key] = ParsePropertySubElement((XmlElement)valueElements.Item(0), name, parserContext); + } + + #endregion + } + return dictionary; + } + private bool ParseMergeAttribute(XmlElement collectionElement, ObjectDefinitionParserHelper helper) { + string val = collectionElement.GetAttribute(ObjectDefinitionConstants.MergeAttribute); + if (ObjectDefinitionConstants.DefaultValue.Equals(val)) { + val = helper.Defaults.Merge; + } + return ObjectDefinitionConstants.TrueValue.Equals(val); + } + + /// + /// This method overrides SelectNodes from ObjectsNamespaceParser because the original method + /// picks up the NamespaceURI from the nvelocity which causes element.SelectNodes to return an empty + /// list. Consider removing. + /// + protected new XmlNodeList SelectNodes(XmlElement element, string childElementName) { + XmlNamespaceManager nsManager = new XmlNamespaceManager(new NameTable()); + nsManager.AddNamespace(GetNamespacePrefix(element), Namespace); + return element.SelectNodes(GetNamespacePrefix(element) + ":" + childElementName, nsManager); + } + + + private string GetNamespacePrefix(XmlElement element) { + return StringUtils.HasText(element.Prefix) ? element.Prefix : "spring"; + } + } + + #region Element & Attribute Name Constants /// /// Template definition constants @@ -426,5 +549,4 @@ namespace Spring.Template.Velocity.Config { public const string SpringResourceLoaderClass = "Spring.Template.Velocity.SpringResourceLoader; Spring.Template.Velocity"; } #endregion - } } \ No newline at end of file diff --git a/test/Spring/Spring.Template.Velocity.Tests/Template/Velocity/Config/TemplateNamespaceParserTests.cs b/test/Spring/Spring.Template.Velocity.Tests/Template/Velocity/Config/TemplateNamespaceParserTests.cs index 99f142a1..0452dda6 100644 --- a/test/Spring/Spring.Template.Velocity.Tests/Template/Velocity/Config/TemplateNamespaceParserTests.cs +++ b/test/Spring/Spring.Template.Velocity.Tests/Template/Velocity/Config/TemplateNamespaceParserTests.cs @@ -22,6 +22,7 @@ using System.Collections; using System.IO; +using System.Text; using Commons.Collections; using NUnit.Framework; using NVelocity.App; @@ -42,9 +43,9 @@ namespace Spring.Template.Velocity.Tests.Template.Velocity.Config { public class TemplateNamespaceParserTests : VelocityEngineTestBase { #region convinience properties aliases private const string PropertyModificationCheck = - TemplateNamespaceParser.TemplateDefinitionConstants.PropertyResourceLoaderModificationCheckInterval; + TemplateDefinitionConstants.PropertyResourceLoaderModificationCheckInterval; private const string PropertyResourceLoaderCachce = - TemplateNamespaceParser.TemplateDefinitionConstants.PropertyResourceLoaderCaching; + TemplateDefinitionConstants.PropertyResourceLoaderCaching; #endregion #region test constants @@ -61,7 +62,7 @@ namespace Spring.Template.Velocity.Tests.Template.Velocity.Config { VelocityEngine velocityEngine = appContext.GetObject("cnFileVelocityEngine") as VelocityEngine; Assert.IsNotNull(velocityEngine, "velocity engine is null"); Assert.AreEqual(VelocityConstants.File, getSingleProperty(velocityEngine, RuntimeConstants.RESOURCE_LOADER), "incorrect resource loader"); - Assert.AreEqual(TemplateNamespaceParser.TemplateDefinitionConstants.FileResourceLoaderClass, getSingleProperty(velocityEngine, + Assert.AreEqual(TemplateDefinitionConstants.FileResourceLoaderClass, getSingleProperty(velocityEngine, TemplateNamespaceParser.getResourceLoaderProperty(VelocityConstants.File, VelocityConstants.Class)), "incorrect resource loader type"); Assert.AreEqual(new string[]{"Template/Velocity/", "Template/"}, velocityEngine.GetProperty( TemplateNamespaceParser.getResourceLoaderProperty(VelocityConstants.File, VelocityConstants.Path)), "incorrect resource loader path"); @@ -82,7 +83,7 @@ namespace Spring.Template.Velocity.Tests.Template.Velocity.Config { VelocityEngine velocityEngine = appContext.GetObject("cnAssemblyVelocityEngine") as VelocityEngine; Assert.IsNotNull(velocityEngine, "velocity engine is null"); Assert.AreEqual(VelocityConstants.Assembly, getSingleProperty(velocityEngine, RuntimeConstants.RESOURCE_LOADER), "incorrect resource loader"); - Assert.AreEqual(TemplateNamespaceParser.TemplateDefinitionConstants.AssemblyResourceLoaderClass, getSingleProperty(velocityEngine, + Assert.AreEqual(TemplateDefinitionConstants.AssemblyResourceLoaderClass, getSingleProperty(velocityEngine, TemplateNamespaceParser.getResourceLoaderProperty(VelocityConstants.Assembly, VelocityConstants.Class)), "incorrect resource loader type"); Assert.AreEqual("Spring.Template.Velocity.Tests", getSingleProperty(velocityEngine, TemplateNamespaceParser.getResourceLoaderProperty(VelocityConstants.Assembly,VelocityConstants.Assembly)), "incorrect resource loader path"); @@ -99,9 +100,9 @@ namespace Spring.Template.Velocity.Tests.Template.Velocity.Config { public void TestSpringBasedConfig() { VelocityEngine velocityEngine = appContext.GetObject("cnSpringVelocityEngine") as VelocityEngine; Assert.IsNotNull(velocityEngine, "velocity engine is null"); - const string PropertySpring = TemplateNamespaceParser.TemplateDefinitionConstants.Spring; + const string PropertySpring = TemplateDefinitionConstants.Spring; Assert.AreEqual(PropertySpring, getSingleProperty(velocityEngine, RuntimeConstants.RESOURCE_LOADER), "incorrect resource loader"); - Assert.AreEqual(TemplateNamespaceParser.TemplateDefinitionConstants.SpringResourceLoaderClass, getSingleProperty(velocityEngine, + Assert.AreEqual(TemplateDefinitionConstants.SpringResourceLoaderClass, getSingleProperty(velocityEngine, TemplateNamespaceParser.getResourceLoaderProperty(PropertySpring, VelocityConstants.Class)), "incorrect resource loader type"); // no way to test the path property other than trying to actually perform a merge (it is set in velocity engine as an application attribute which is not exposed) Assert.AreEqual(DEFAULT_CACHE_SIZE, velocityEngine.GetProperty(RuntimeConstants.RESOURCE_MANAGER_DEFAULTCACHE_SIZE), "incorrect default cache size"); @@ -129,6 +130,15 @@ namespace Spring.Template.Velocity.Tests.Template.Velocity.Config { Assert.AreEqual("TestDescription", getSingleProperty(velocityEngine, descProp), "incorrect description"); } + [Test] + public void TestLocalConfig() { + VelocityEngine velocityEngine = appContext.GetObject("cnVelocityEngineLocalConfig") as VelocityEngine; + Assert.IsNotNull(velocityEngine, "velocity engine is null"); + + Assert.AreEqual(Encoding.UTF8.WebName.ToUpper(), getSingleProperty(velocityEngine, RuntimeConstants.INPUT_ENCODING), "incorrect input encoding value"); + Assert.AreEqual(TEST_VALUE, getSingleProperty(velocityEngine, "myproperty.mysubproperty"), "incorrect custom property value"); + } + /// /// Test a custom resource loader definition /// diff --git a/test/Spring/Spring.Template.Velocity.Tests/Template/Velocity/VelocityEngineFactoryObjectTests.xml b/test/Spring/Spring.Template.Velocity.Tests/Template/Velocity/VelocityEngineFactoryObjectTests.xml index 567bb04d..ad7bb506 100644 --- a/test/Spring/Spring.Template.Velocity.Tests/Template/Velocity/VelocityEngineFactoryObjectTests.xml +++ b/test/Spring/Spring.Template.Velocity.Tests/Template/Velocity/VelocityEngineFactoryObjectTests.xml @@ -20,18 +20,25 @@ - + - - - - - - + + + + + + + + + + + + +