Frank Fusion

Sunday, 20 April 2008

Scatting with ColdSpring

I really like ColdSpring - quiet, clean and powerful - mmmm (forgive me, I'm very tired). There is one thing that I have found that bugs me a little however; the verbosity of defining aspects and advisors.

Here's a quick xml sample of creating a really simple aspect and applying its advice to a bean:


<beans>
<!-- state checker advice and advisor -->
<bean id="stateChecker" class="aopxml.stateChecker" />
<bean id="stateCheckerAdvisor" class="coldspring.aop.support.NamedMethodPointcutAdvisor">
<property name="advice">
<ref bean="stateChecker" />
</property>
<property name="mappedNames">
<value>*</value>
</property>
</bean>

<!-- test component with proxy -->
<bean id="testerTarget" class="aopxml.tester" singleton="false" />
<bean id="tester" class="coldspring.aop.framework.ProxyFactoryBean" singleton="false">
<property name="target">
<ref bean="testerTarget" />
</property>
<property name="interceptorNames">
<list>
<value>stateCheckerAdvisor</value>
</list>
</property>
</bean>

</beans>

This is example is from my blogpost http://fusion.dominicwatson.co.uk/2008/03/stricter-oop-using-aop.html.

Worse still, if I want to apply my aspect to any another beans, I would have to explicitly do so by defining a proxy for each bean.

So what's the solution?

The first thing I thought was to have specific AOP xml tags for the configuration that would allow you to define aspects and instruct ColdSpring to automagically create proxies for you based on the component name rules you supply it.

I posted this thought on the ColdSpring google group and the reply made me look to the Spring framework (on which ColdSpring is based) - what does the Spring framework do? Well, it does pretty much exactly as I suggested:

http://static.springframework.org/spring/docs/2.5.x/reference/aop.html#aop-schema

So, with a minor adaption for ColdFusion (though not budging from the Spring Xml schema), here is how the above Xml example could look:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop">
<!-- 'new' aop tags -->
<aop:config>
<aop:aspect id="stateCheckerAspect" ref="stateChecker">
<aop:around pointcut="*.*" method="invokeMethod"/>
</aop:aspect>
</aop:config>

<!-- 'aspect' beans -->
<bean id="stateChecker" class="temp.stateChecker" />

<!-- 'regular' beans -->
<bean id="tester" class="temp.tester"/>
</beans>

The above XML defines an Around advice that will be applied to every bean and every method in the factory. Of course, different rules could be setup and I think the benefit is quite graphical.

This got me all excited and sent me on an all night coding spree. The result was a new ColdSpring bean factory that would take the Xml above and make it work. Its just a proof of concept and a little rough around the edges but it shows what can be done with reasonable ease (thanks to the excellent codebase already in ColdSpring).

The extended factory will indeed parse most of the aop elements defined in the Spring schema. I won't go into detail as they are very well documented in the Spring documentation that I linked to. However, here is a quick example of an aop:config that the extended factory will do stuff with:

<aop:config>
<aop:pointcut id="aopxmlPackagePC" expression="aopxml.*.Add*"/>
<aop:aspect id="stateCheckerAspect" ref="stateChecker">
<aop:around pointcut-ref="aopxmlPackagePC" method="invokeMethod"/>
</aop:aspect>
<aop:aspect id="someErrorCatchingAspect" ref="someAdviceBean">
<aop:pointcut id="thisAppServices" expression="com.myCo.thisApp.services.*.*"/>
<aop:after-throwing pointcut-ref="thisAppServices" method="afterThrowing"/>
</aop:aspect>
</aop:config>

You can download the simple working example here. You may need to create a mapping to whereever you unzip it (mapping called 'aopxml').

A disclaimer: this is completely unofficial and just a proof of concept - I'm just scatting. Enjoy :)

Labels: , , , ,

Tuesday, 25 March 2008

XmlSearch and default Namespaces

This pops up frequently enough on the cf-talk list and around the blogosphere. To get an idea of the problem you can read this blog post that usually gets quoted as the answer:

http://www.talkingtree.com/blog/index.cfm/2005/11/18/XmlSearchNoNameNamespace

If you were keen, you may have noted the comments that refer to the technique not working when there are multiple, nested default namespaces. Take the following Xml for example:

<?xml version="1.0"?>
<foo xmlns="http://dominicwatson.co.uk/foo/">
<foochild>
<bar xmlns="http://dominicwatson.co.uk/bar/">
<barchild>lamb</barchild>
</bar>
</foochild>
</foo>

Doing XmlSearch(theXml, '//:foochild')will return results but XmlSearch(theXml, '//:barchild') will not. There are ways around this using XPath but they can become very cumbersome if your XPath is anything like complicated.

The source of the problem is ColdFusion's pretty poor provision of XPath goodiness. Other languages provide XPath querying methods that take a namespace mapping object as an argument. This allows you to define custom prefixes to use with the namespace URIs in your XPath queries. Here is a .net blog post as an example:

http://weblogs.asp.net/wallen/archive/2003/04/02/4725.aspx

Enter BetterXml 1.0!

In version 1 of my BetterXml components, just released, I have added the ability to add custom prefix mappings to Xml documents in ColdFusion (much in the way other languages allow). Using a betterXml object loaded with the xml above, we can now do something like this:

<cfscript>
oXml.MapNamespace('http://dominicwatson.co.uk/foo/', 'f');
oXml.MapNamespace('http://dominicwatson.co.uk/bar/', 'b');
results = oXml.Search('//b:barchild);
</cfscript>


Problem solved! You can download the latest version of the project on riaforge:

http://betterXml.riaforge.org/

Labels: , ,

Sunday, 23 September 2007

BetterXML Beta Release!

http://betterxml.riaforge.org/

After some initial learning about XML and handling it in ColdFusion 6.1 & 7, I came to the conclusion that ColdFusion wasn't the best when it came to XML.

When reading and transforming XML, ColdFusion does a great job and is at its usual brilliantly quick. Where I think it gets cumbersome is in maintenance scenarios where you have to edit a given XML file. For example, you may have some XML that is being used as the sole data source and needs updating whenever a user adds item x or deletes item z, etc.

An ideal solution to this is to use XPath to quickly navigate the XML in readiness for your CRUD operations. ColdFusion's XMLSearch() method is not suitable in this case however, as it only returns data from an XPath query. To navigate the XML using XPath, we need the XPath query to return a nodeset that is a collection of pointers to the original data. Once we have this collection of pointers, we can go about performing our CRUD operations on the data.

This is where BetterXML comes in. BetterXML is a set of two ColdFusion Components; an XML reader and an XML editor, the editor extending the reader. They both use the org.apache.xpath.XPathAPI java object that lets them quickly navigate the data using XPath.

The public methods are as follows:

XML Reader:
  • Init( src ) - Returns an instance of the object, loading the XML if supplied
  • Load( src ) - loads XML from either an xml string, file or URL
  • Search( XPath, filter ) - returns an array of either strings or simplified structures based on the results of the XPath query
  • Count( XPath ) - returns the number of results returned by the XPath query
  • Names( XPath ) - returns the names of the attributes and elements returned by the XPath query
  • XML() - returns the XML as a string
XML Editor:
  • Init( src ) - Returns an instance of the object, loading the XML if supplied
  • Write( destination ) - Writes the XML to file
  • CreateElement( XPath, Name, Value ) - Creates an element in all the parents returned by the XPath query. The value of the element can be a simple value or a structure to write nested elements.
  • CreateAttribute( XPath, Name, Value ) - Creates an attribute in all the elements returned by the XPath query
  • Update( XPath, Value ) - Updates all the elements returned by the XPath query. The updated value can be a simple value or a structure to write nested elements.
  • Delete( XPath ) - Deletes all the elements returned by the XPath query
A quick example
Lets say you have a huge XML file that is full of comments that are now redundant. To delete all the comments from the file we can do:

<cfinvoke component="BetterXML_Editor" method="init" src="#myFile#" returnVariable="xmlFile"/>
<cfscript>
xmlFile.Delete('//comment()');
xmlFile.Write(myFile);
</cfscript>

The code is minimal and the load times hugely improved when compared to using native ColdFusion functions to achieve the same end :)

Dom

Labels: ,