Creating A Metadata-Aware ColdSpring Advice
I've been working with Flex a lot recently. One of the first things that I did was create some ColdSpring AOP Advices that would take data coming back from my service layer and convert it into Value Objects so that they can be automatically converted into ActionScript Value Objects on the Flex side. This seemed to work pretty well and did exactly what I needed it to.
However, in the quest for creating something more generic and reusable, I decided to build an Advice that I could feed an XML file. This file would define how I want the data returned by the various services to be converted into Value Objects. This was needed because the way a product query is converted is different than, say, how a shopping cart CFC is converted. Basically, I wanted to define what Value Object Converter to use for a given service, what actual Value Object components to use, and even be able to define different converters for different methods within a service in case it wouldn't work to define one converter for all of the methods in the service.
I got all this to work and it was nice. However, at this point I realized that this idea could apply far beyond my single case of setting up Value Object converters. I could define any data to associate with a specific ColdSpring-managed bean, or any methods within those beans. What I've really got now is a generic Advice that can be detect and use any metadata I want to associate with a bean or a method!
To start with my Value Object converter example, I would attach my Advice to my Remote Proxy in the ColdSpring configuration:
<bean id="remoteProductService" class="coldspring.aop.framework.RemoteFactoryBean" lazy-init="false"> <property name="interceptorNames"> <list> <value>VOConverterAdvisor</value> </list> </property> <property name="target"> <ref bean="productService" /> </property> <property name="serviceName"> <value>RemoteProductService</value> </property> <property name="relativePath"> <value>/presentation/flexbookstore/remote/</value> </property> <property name="remoteMethodNames"> <value>*</value> </property> <property name="beanFactoryName"> <value>BeanFactory</value> </property> </bean> <bean id="VOConverterAdvice" class="presentation.common.components.VOConverterAdvice"> <property name="metadataConfig"> <value>/myproject/config/advicemetadata.xml</value> </property> </bean> <bean id="VOConverterAdvisor" class="coldspring.aop.support.NamedMethodPointcutAdvisor"> <property name="advice"> <ref bean="VOConverterAdvice" /> </property> <property name="mappedNames"> <value>*</value> </property> </bean>
Probably looks familiar to people who have used ColdSpring's AOP capabilities (and if you haven't, don't worry, it looks worse than it is). However, notice that I am passing a property called "metadataConfig" to my Advice. This is where the magic happens.
My VOConverterAdvice extends a component called AbstractMetadataAwareAdvice. This component reads in the metadata file specified by the path, processes it, caches it, and has it available for use by the VOConverterAdvice any time it handles an incoming method call.
The advicemetadata.xml file is very simple:
metadata> <target name="ProductService" vo="presentation.common.components.ProductVO" /> </metadata>
Now my generic VOConverterAdvice knows that when a method is invoked on my ProductService, I want the converter to create ProductVO value objects from the returned data.
OK, you might think that doesn't look particularly mind blowing. But I can also do this:
metadata> <target name="CartService" converter="CartVOConverter" cartVO="presentation.common.components.CartVO"> <method name="getCart" productVO="presentation.common.components.ProductVO" /> </target> </metadata>
So when the CartService is called, the Advice now has a bunch of useful information at its disposal. It knows that I'm using a custom converter named "CartVOConverter". It knows the path to the CartVO value object that I want to use. Further, when I call getCart() on CartService, not only is all of the previous data available, but it also gets a path to a ProductVO.
So I can define some global data to associate with the entire service, as well as any specific data to associate with one or more methods. The data in the method will override any data defined for the whole service. This is extremely useful because it opens up a huge amount of capability to your Advices by giving them information that they otherwise would never be able to know.
This can be applied to any data you wish you could feed to your Advices. Consider:
metadata> <target name="CartService" converter="CartVOConverter" logVariables="foo,bar,blah" cartVO="presentation.common.components.CartVO"> <method name="getCart" logVariables="zoo,goo,blah" productVO="presentation.common.components.ProductVO" /> </target> <target name="ProductService" logVariables="foo,bar,blah" vo="presentation.common.components.ProductVO" /> </metadata>
I can tell a Logging Advice what variables I want logged, but at the overall service level or for specific methods. Hopefully you can see where this is going. My Advices have become vastly more intelligent because they have much more information to use when they run.
Again, all of this is cached, so there should be virtually no performance cost for being able to do this. Big benefit at low cost...I like the sound of that.
Anyway, I'm curious to hear what other ColdSpringers (is that a word?) think about this idea. Obviously, I think this is cool, because I wrote it. Do others see possibilities here? Or possibly, have I missed an easier route to solve the problem? Please let me know!




It was similar, in that we both added extra "metadata" to the method call, and different in that we accomplished it in very different ways.
Mine's not as clean as yours as it relied on adding extra attributes to <cfcomponent> and <cffunction> tags down in my model that implied knowledge of its use as a service layer, but possibly a little easier to deal with (kind of like the Hibernate XML vs. Hibernate Annotations debate).
Basically, if I had a service method that returned a query, I'd annotate the method with how to handle the results as value objects:
<!--- in product service --->
<cffunction name="listProducts" returntype="query" returnVO="com.firemoss.magiccart5000.vo.ProductVO">
<!--- stuff --->
</cffunction>
If I needed something other than a straight 1:1 query row -> VO converter, I'd state which converter should be used (as you did), adding:
voConverter="productVOConverter"
...where productVOConverter is configured as a bean in ColdSpring.
As the app grew, we also hit situations where individual business objects needed representations of their states as Value Objects (instead of a straight send-the-cfc-down-the-wire), in which case we had to convert them as a result of service methods (and "upsize" received VOs to the full-scale business objects). Taking a similar approach, we had:
<!--- Product.cfc: a business object --->
<cfcomponent vo="com.firemoss.magiccart5000.vo.ProductVO" />
<!--- ProductVO.cfc: a Data Transfer Object --->
<cfcomponent businessObject="com.firemoss.magiccart5000.model.Product" />
Similar to your approach, advice applied to the remote proxies invoked the actual conversion. We just stored metadata in a different place.
DW
Do you happen to have a runnable collection of files that I can set up in my local instance? I would like to give this a shot locally so I can give you better feedback.
DW
When I do get it to work, however, this code seems to be exactly what I will need. I really want to use Transfer, and I really want to use it right.
It still doesn't seem to be calling the advice because when my page calls the method from the service (via CS of course) it still is returning a query.