Lightwire XML placeholder bug

February 18, 2010

I'm pretty sure I've found a bug in this is a bug in Lightwire when you configure it using an XML bean definition with placeholders.

I'm working on a application at the moment where the developer hadn't used a bean factory to manage dependancies, so I'm in process of adding one to make my left easier.

Peter Bell's Lightwire seemed like the obvious as it is lightweight and handles singletons and transients. Being slighty odd, I like to configure my bean definitions using XML (I find it easier to read and means I can switch to ColdSpring if required without too much refactoring). One of the things that you can do with XML bean configs (in Lightwire and ColdSpring) is use placeholders for variables. So here is a simple example of what I wrote:


<?xml version="1.0" encoding="UTF-8"?>
<beans>
<bean id="Foo" class="lightwire.XMLBug.model.Foo">
<constructor-arg name="name"><value>${name}</value></constructor-arg>
</bean>
<bean id="Bar" class="lightwire.XMLBug.model.Bar">
<constructor-arg name="username"><value>${name}</value></constructor-arg>
</bean>
</beans>

Where you can see ${name}, I want Lightwire to substitute in a value at runtime. This is great because I can use variables in XML and configure the bean factory depending on the environment it is running in. For example; you probably have different email addresses, datasource names etc for your production and development versions.

However when I ran my application, only the first placeholder was being substituted. So I wrote some simple code to test if I was doing something stupid.


<!--- index.cfm --->
<cfset myBeanConfig = createObject( "component","BeanConfig" ).init()>
<cfset myBeanFactory = createObject( "component","lightwire.LightWire" ).init( myBeanConfig )>

<cfset Foo = myBeanFactory.getBean( "Foo" )>
<cfset Bar = myBeanFactory.getBean( "Bar" )>

<cfoutput>

<strong>LightWire XML dynamic properties test</strong><br />

<p>Foo.getName() returns: #Foo.getName()#</p>

<p>Bar.getUserName() returns: #Bar.getUserName()#</p>

</cfoutput>

<!--- Foo.cfc --->
<cfcomponent name="Foo" output="false">

<cffunction name="init" output="false" returntype="any">
<cfargument name="name" type="string" required="true">
<cfscript>
variables.name = arguments.name;
return this;
</cfscript>
</cffunction>

<cffunction name="getName" output="false" returntype="string">
<cfscript>
return variables.name;
</cfscript>
</cffunction>

</cfcomponent>

<!--- Bar.cfc --->
<cfcomponent name="Bar" output="false">

<cffunction name="init" output="false" returntype="any">
<cfargument name="username" type="string" required="true">
<cfscript>
variables.username = arguments.username;
return this;
</cfscript>
</cffunction>

<cffunction name="getUserName" output="false" returntype="string">
<cfscript>
return variables.username;
</cfscript>
</cffunction>

</cfcomponent>

This is the output I was getting

LightWire XML dynamic properties test 
Foo.getName() returns: aliaspooryorik
Bar.getUserName() returns: ${name}

After some step-debugging I found out the Lightwire uses the name attribute of the constructor-arg node, rather than the placeholder name of the value node. This is the block of code in question from the translateBeanChildren method in BaseConfigObject.cfc:


if ((structKeyExists(arguments,"props")) and (structKeyExists(arguments.props,children[i].XmlAttributes["name"])))
{
property = arguments.props[#ReplaceList(children[i].value.XmlText,"${,}",",")#];
}
else
{
property = children[i].value.XmlText;
}

To get the behavious I was expecting, I changed the above code to:


// set property to node value as a default
property = children[i].value.XmlText;

// find out if the value has placeholder syntax - for example ${abc}
if ((structKeyExists(arguments,"props")) and (refind("^\$\{\w+\}$", property)))
{
// it is a placeholder so try to find a matching key in the props
if (structKeyExists(arguments.props,ReplaceList(property,"${,}",",")))
{
property = arguments.props[ReplaceList(property,"${,}",",")];
}
}

Now when I run my test app I get the result that I would expect

LightWire XML dynamic properties test 
Foo.getName() returns: aliaspooryorik
Bar.getUserName() returns: aliaspooryorik

3 comments

  1. Hi John,

    Thanks for posting this. I don't use and didn't write the XML config (I agree with the benefits, they just aren't important for my use cases!). That said I'll try to find time this weekend to review and (if appropriate) to patch.

    Comment by Peter Bell – February 18, 2010
  2. Hi Peter, thanks for looking into this. I can send you a patch against the version on riaforge svn if that helps? I could also include the support I wrote for the import node in bean configs :)
    www.aliaspooryorik.com/blog/index.cfm/e/posts.details/post/lightwire-import-node-support-233

    Comment by John Whish – February 19, 2010
  3. Hi John,

    Looks like a bug! I've patched it and will blog/tweet. Doesn't appear to affect ColdBox version although it's so different I just passed that along to Luis to check on (only just now, so it may take a little while).

    Thanks so much for bringing this up. Patch available for download at project site:
    lightwire.riaforge.org/

    Comment by Peter Bell – February 27, 2010

Leave a comment

If you found this post useful, interesting or just plain wrong, let me know - I like feedback :)

Please note: If you haven't commented before, then your comments will be moderated before they are displayed.