My Transfer Decorator Bean Injector Observer Is Now Available
I'm finally releasing my Bean Injector Observer, which will allow you to automatically inject ColdSpring-managed beans into your Transfer Decorator objects. I've found that this makes it much easier to create decorators that act as rich business objects.
I had been holding off on releasing this because it will probably be included in the near future in either the ColdSpring or Transfer bleeding edge releases. However, a lot of people have contacted me privately or on the Transfer list about wanting to use this, so I'm going to make it available on my site until we see it appear in one of the framework downloads.
The content of the release notes follows, which should explain what it is and how it works. I hope people find this useful! Thanks to Sean Corfield and Mark Mandel for their input and review of the code to help me make sure it works as well as possible.
File Name:
TDOBeanInjectorObserver.cfc (Transfer Decorator Object Bean Injector Observer)
Description:
This is a Transfer Observer that will autowire your Transfer Decorators with matching beans from ColdSpring when the Decorator is created. This makes it much easier to create "rich" Decorators that can handle much more business logic than standard Transfer Objects. The dependencies are cached by this Observer for performance. After the first instance of a Decorator is created, all subsequent Decorators of that type will have their dependencies injected using cached information. The component is thread-safe.
Usage:
Usage of the Observer is fairly straightforward. The ColdSpring XML file might look like this:
<constructor-arg name="datasourcePath"><value>/project/config/datasource.xml</value></constructor-arg>
<constructor-arg name="configPath"><value>/project/config/transfer.xml</value></constructor-arg>
<constructor-arg name="definitionPath"><value>/project/transfer/definitions</value></constructor-arg>
</bean>
<bean id="transfer" factory-bean="transferFactory" factory-method="getTransfer" />
<bean id="TDOBeanInjectorObserver" class="tests.transfer.TDOBeanInjectorObserver" lazy-init="false">
<constructor-arg name="transfer"><ref bean="transfer" /></constructor-arg>
</bean>
<bean id="validatorFactory" class="components.ValidatorFactory" />
Your ColdSpring configuration must be set to inject the Transfer object into the Observer as a constructor argument.
The Observer will register itself with Transfer using the transfer.addAfterNewObserver() method. To ensure that this happens at application startup, you have two options:
1. Use the latest version of ColdSpring that supports lazy-init. What this means is that ColdSpring will automatically construct all beans that have lazy-init="false" defined in the ColdSpring XML (as the TDOBeanInjectorObserver bean is in the above config snippet). You tell ColdSpring to construct all non-lazy beans when you create the BeanFactory:
Using this approach, the TDOBeanInjectorObserver will be registerd with Transfer without you have to do anything else.
2. On older versions of ColdSpring, or if you do not wish to use the lazy-init capability, the only additional step required is to create an instance of the Observer after you initialize ColdSpring, like this:
<cfset beanFactory.getBean('TDOBeanInjectorObserver') />
This ensures that the Observer is constructed and registers itself with Transfer.
Your Transfer Decorator would have public setter method(s) for the bean(s) you want to inject, for example:
<cfargument name="validatorFactory" type="any" required="true" hint="ValidatorFactory" />
<cfset variables.instance.validatorFactory = arguments.validatorFactory />
</cffunction>
Once the Observer is registered with Transfer, any time you create a Transfer Decorator, the Observer will automatically inject any dependent beans into it at creation time. So in the above example, as soon as the Decorator is created, it will automatically have the ValidatorFactory injected into it via the setValidatorFactory() method. The end result is that any setters in your Decorators that have matching bean IDs in ColdSpring will have those beans injected automatically. As an additional example, a bean with an ID of "productService" would be autowired into a Decorator that had a public setter method named setProductService(), and so on.
There is an optional constructor argument called "suffixList" that can be supplied. This is a comma-delimited list of propery name suffixes that will be allowed. If you specify a suffixList, the Observer will only inject beans which end in one of the suffixes in the list. For example, if you specify a suffixList of "service", setter methods for setUserService() and setLoginService() would be called, but setter methods for setValidatorFactory() or setContext() would NOT be called. This can be useful in rare situations where your Transfer Object may have database-driven properties that conflict with the names of ColdSpring beans. Most people probably won't need to worry about this, but the option is here in case the issue arises.
In case you have problems determining whether beans are being properly injected into your Decorators, there is an optional init() method argument called "debugMode". By default, this is false. If you set it to true via the ColdSpring XML config file, the component will trace successful dependency injections to the debugging output. It will also rethrow any errors that occur while trying to inject beans into your Decorators. Obviously, ensure that this is remains off in production.






Thanks for releasing yet another superlative piece of software!
My vote would be to include this in ColdSpring, under a "transfer" directory (in the same way that Mach-II and Model-Glue add-ins are today).
An issue that I encountered is that the AfterNewObserver is run by Transfer after the Configure() method. I have a use case in which I need to do something when a Transfer Object is created which relies on a Service. I cannot call the Service in Configure() as it hasn't been injected yet. So I created a new method inside my generic transfer decorator called Setup(). In my AfterNewObserver, after injecting the dependencies I call Setup(). Then, if I have code which needs to be run for a new Transfer Object, and that depends on an injected bean, I simply put that code into Setup(), rather than Configure().
So, my request is that an additional argument be available to init() in your observer which accepts the name of a method to call after all dependent beans have been injected. It could work so that if no method was specified then no method would be called, and anyone not needing this functionality wouldn't have to worry about the additional method call.
That is just my suggestion for how to address this issue, you may have a better idea, but I do think the functionality would be useful.
In case anyone reading this is wondering why this would be useful, here's my use case:
A user places an order. I want the initial status of the order to be "Created". Orders have a child OrderStatus. So, when the order is created I need to set the child OrderStatus to the OrderStatus that has a description of "Created". The primary key to the OrderStatus table is an autogenerated integer, so I cannot just hardcode this ( I don't know what the primary key will be). I need to look up the OrderStatus that corresponds to "Created", and I have a method in my OrderService to do that. I need to call that method to return the proper OrderStatus object, so I can set it in my Transfer Object. The line of code that does this looks like:
setOrderStatus(getOrderService().FindOrderStatus("Created"));
And that line of code goes into my Setup() method, as described above.
Sorry for being so long winded, but I wanted to make sure I accurately explained my idea.
I appreciate any consideration you give to this.
I'm only new to Transfer (and OOP in general) but this makes perfect sense to me. A very nice way to smoothly integrate other objects into the Transfer decorators.
What would be helpful though is to get an idea of the kind of functionality you would build into the decorators which leverages this method. What kind of additional methods do you build in your decorators that you wouldn't have been able to without the observer method?
I'm also wondering whether it could be tempting to put logic into the decorators that should really be kept in a different layer. I.E If Transfer is removed at some point and changed to a different system how easy would it be to recreate the additional decorator functionality.
I'm probably seeing potential issues where there are none, but I think I'm trying to work out what should be kept in the Gateway and what should be in the decorator - if that is a valid way of looking at things in this context.
Thanks for releasing this code Brian - I'll be sure to implement it when I get Transfer working with my first Gateway.. Which should be soon.. :)
<cffunction name="isValid">
<cfset var isValid = false />
<cfset var validator = getValidatorFactory().getValidator('user') />
<cfset var errors = validator.validate(this) />
<cfif not ArrayLen(errors)>
<cfset isValid = true />
</cfif>
<cfreturn isValid />
</cffunction>
Obviously this could be made much more complex, removing the need to pass in the hardcoded string user and having the factory determine dynamically what validator to return. But the point is, without something like the Bean Injector Observer, it's something of a pain to get the Validator into your Transfer Object. Hopefully that helps.
Yes that does help alot, thanks. I like the way your passing the bean into the validator factory via 'this' - nice and clean.
So really this kind of approach is extending the Transfer layer (if that's the right way to describe it). If Transfer was removed at a later date, a new validator factory would need to be written for the new database / persistence mechanism?
The observer is an excellent way to add this functionality when using Transfer.. I'll definetely make use of it.
Thanks,
James.