SPRNET-1237: Fix to parsing of velocity properties in custom namespace

This commit is contained in:
erezmazor
2009-08-01 15:07:11 +00:00
parent 546003e095
commit 4e006d8635
3 changed files with 169 additions and 30 deletions

View File

@@ -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 {
/// <param name="parserContext">the parser context</param>
/// <param name="properties">the properties used to initialize the velocity engine</param>
private void ParseNVelocityProperties(XmlElement element, ParserContext parserContext, IDictionary<string, object> 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;
}
/// <summary>
/// Helper class to access the ParseDictionaryElement of the ObjectsNamespaceParser
/// todo: remove this and externalize the logic in ObjectsNamespaceParser.ParseDictionaryElement
/// </summary>
internal sealed class ObjectNamespaceParserHelper : ObjectsNamespaceParser {
public IDictionary ParseDictionaryElementInternal(XmlElement element, string name, ParserContext parserContext) {
return ParseDictionaryElement(element, name, parserContext);
}
}
/// <summary>
/// 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
}
/// <summary>
/// Helper class to access the ParseDictionaryElement of the ObjectsNamespaceParser
/// todo: remove this and externalize the logic in ObjectsNamespaceParser.ParseDictionaryElement
/// </summary>
internal class ObjectNamespaceParserHelper : ObjectsNamespaceParser {
/// <summary>
/// Gets a dictionary definition.
/// </summary>
/// <param name="mapEle">The element describing the dictionary definition.</param>
/// <param name="name">The name of the object (definition) associated with the dictionary definition.</param>
/// <param name="parserContext">The namespace-aware parser.</param>
/// <returns>The dictionary definition.</returns>
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);
}
/// <summary>
/// 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.
/// </summary>
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
/// <summary>
/// Template definition constants
@@ -426,5 +549,4 @@ namespace Spring.Template.Velocity.Config {
public const string SpringResourceLoaderClass = "Spring.Template.Velocity.SpringResourceLoader; Spring.Template.Velocity";
}
#endregion
}
}

View File

@@ -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");
}
/// <summary>
/// Test a custom resource loader definition
/// </summary>

View File

@@ -20,18 +20,25 @@
<nv:spring uri="assembly://Spring.Template.Velocity.Tests/Spring.Template.Velocity.Tests.Template.Velocity/"/>
</nv:resource-loader>
</nv:engine>
<nv:engine id="cnVelocityEngineConfig" config-file="file://Template/Velocity/config.properties" override-logging="false" />
<nv:engine id="cnVelocityEngingCustomResourceLoader">
<nv:resource-loader default-cache-size="200" modification-check-interval="30" template-caching="true">
<nv:custom name="myResourceLoader"
description="A custom resource loader"
type="Spring.Template.Velocity.Tests.Template.Velocity.Config.TestCustomResourceLoader, Spring.Template.Velocity.Tests"
path="Template/Velocity/"/>
</nv:resource-loader>
</nv:engine>
<nv:engine id="cnVelocityEngineLocalConfig" >
<nv:nvelocity-properties>
<entry key="input.encoding" value="UTF-8"/>
<entry key="myproperty.mysubproperty" value="TEST_VALUE"/>
</nv:nvelocity-properties>
</nv:engine>
<nv:engine id="cnVelocityEngingCustomResourceLoader">
<nv:resource-loader default-cache-size="200" modification-check-interval="30" template-caching="true">
<nv:custom name="myResourceLoader"
description="A custom resource loader"
type="Spring.Template.Velocity.Tests.Template.Velocity.Config.TestCustomResourceLoader, Spring.Template.Velocity.Tests"
path="Template/Velocity/"/>
</nv:resource-loader>
</nv:engine>
<!-- Does file system access with hot tracking with NVelocity assembly resource loader -->
<object id="assemblyBasedVelocityEngine" type="Spring.Template.Velocity.VelocityEngineFactoryObject, Spring.Template.Velocity">
<property name="VelocityProperties">