Saturday, January 2, 2010

Fun with ColdSpring Post Processors

One really powerful feature of ColdSpring that I love is the ability to define factory post processors. I've blogged about how I've used post processors to simulate the "beans" scope in my applications, which is essentially annotation-based autowiring.

While autowiring helps keep my ColdSpring.xml definition file to a minimum, things tends to get a little redundant. Here's what my XML starts to look like:

ColdSpring.xml

<beans default-autowire="no">

<bean id="productController" class="com.app.controllers.ProductController" />
<bean id="productService" class="com.app.services.ProductService" />
<bean id="productGateway" class="com.app.model.product.ProductGateway" />

<bean id="securityController" class="com.app.controllers.SecurityController" />
<bean id="securityService" class="com.app.services.SecurityService" />
<bean id="securityGateway" class="com.app.model.security.SecurityGateway" />

<bean id="userController" class="com.app.controllers.UserController" />
<bean id="userService" class="com.app.services.UserService" />
<bean id="userGateway" class="com.app.model.user.UserGateway" />

<bean id="fooController" class="com.app.controllers.FooController" />
<bean id="fooService" class="com.app.services.FooService" />
<bean id="fooGateway" class="com.app.model.foo.FooGateway" />

<bean id="barController" class="com.app.controllers.BarController" />
<bean id="barService" class="com.app.services.BarService" />
<bean id="barGateway" class="com.app.model.bar.BarGateway" />

<!-- etc... -->

<bean id="beansScopeInjector" class="com.util.BeansScopePostProcessor" factory-post-processor="true" />

<bean id="helpers" class="com.util.HelpersScopePostProcessor" factory-post-processor="true">
<property name="directories">
<list>
<value>/com/app/helpers/</value>
</list>
</property>
</bean>

</beans>


Basically each area of concern (products, security, users, etc...) has a corresponding controller, service, and gateway that all follow similar naming conventions and folder structures. Wouldn't it be nice if I didn't have to explicitly define each bean, but instead my application just knew how to create its own bean definitions based on conventions? I thought it would, so I created BeanDetector.cfc.

BeanDetector

<bean id="beanDetector" class="com.util.BeanDetector" factory-post-processor="true">
<property name="directories">
<list>
<value>/com/app/controllers/</value>
<value>/com/app/model/</value>
<value>/com/app/services/</value>
</list>
</property>
<property name="patterns">
<list>
<value>[\w]+Controller</value>
<value>[\w]+Gateway</value>
<value>[\w]+Service</value>
</list>
</property>
</bean>


Inside your ColdSpring.xml, you define the BeanDetector and tell it what directories to scan for CFCs. It will scan recursively deep, so in my case I don't need to worry about nested folders in my /model directory. You can also pass in an optional array of regular expressions to match the file name against to exlude non-singleton beans. Here's the code:

BeanDetector.cfc

component accessors="true" {

property directories;
property patterns;
property autowire;

public any function init() {

variables.directories = [];
variables.patterns = [];
variables.autowire = "no";

}

public void function postProcessBeanFactory(required any beanFactory) {

var i = "";
var j = "";
var k = "";

var beans = {};

for (i=1; i <= arrayLen(variables.directories); i++) {

var directory = expandPath(variables.directories[i]);

var classPath = convertDirectoryToClassPath(variables.directories[i]);

var components = directoryList(directory, true, "query", "*.cfc");

for (j=1; j <= components.recordCount; j++) {

var bean = {};
bean.id = listFirst(components.name[j], ".");

var folder = replaceNoCase(components.directory[j] & "\", directory, "");

folder = convertDirectoryToClassPath(folder);

if (folder == '') {
bean.class = classPath & "." & bean.id;
}
else {
bean.class = classPath & "." & folder & "." & bean.id;
}

if (!structKeyExists(beans, bean.id) && !beanFactory.containsBean(bean.id)) {

if (arrayIsEmpty(variables.patterns)) {
beans[bean.id] = bean;
}
else {

for (k=1; k <= arrayLen(variables.patterns); k++) {

if (reFindNoCase(variables.patterns[k], bean.id)) {
beans[bean.id] = bean;
break;
}

}

}

}

}

}

for (i in beans) {

arguments.beanFactory.createBeanDefinition(
beanID=beans[i].id,
beanClass=beans[i].class,
children=[],
isSingleton=true,
isInnerBean=false,
autowire=variables.autowire);

}

}

private string function convertDirectoryToClassPath(required string directory) {

arguments.directory = replace(arguments.directory, "\", "/", "all");

return arrayToList(listToArray(arguments.directory, "/"), ".");

}

}


Now I can remove all those bean definitions and everything will work great, right? Not exactly. Here's what my updated ColdSpring.xml looks like:

ColdSpring.xml

<beans default-autowire="no">

<bean id="beanDetector" class="com.util.BeanDetector" factory-post-processor="true">
<property name="directories">
<list>
<value>/com/app/controllers/</value>
<value>/com/app/model/</value>
<value>/com/app/services/</value>
</list>
</property>
<property name="patterns">
<list>
<value>[\w]+Controller</value>
<value>[\w]+Gateway</value>
<value>[\w]+Service</value>
</list>
</property>
</bean>

<bean id="beansScopeInjector" class="com.util.BeansScopePostProcessor" factory-post-processor="true" />

<bean id="helpers" class="com.util.HelpersScopePostProcessor" factory-post-processor="true">
<property name="directories">
<list>
<value>/com/app/helpers/</value>
</list>
</property>
</bean>

</beans>


You'll notice I'm using multiple factory post processors. It's OK to use multiple post processors, but in my case the post processors need to execute in a certain order: the BeanDetector needs to create all my additional bean definitions before my other post processors can add the "beans" and "helpers" scopes. However, ColdSpring doesn't care about the order of your bean definitions - it stores everything inside a struct. To get around this, I created an OrderedPostProcessor.cfc to manage the order in which my post processors are executed. One post processor to rule them all.

OrderedPostProcessor

<bean id="orderedPostProcessor" class="com.util.OrderedPostProcessor" factory-post-processor="true">
<property name="postProcessors">
<list>
<ref bean="beanDetector" />
<ref bean="beansScopeInjector" />
<ref bean="helpers" />
</list>
</property>
</bean>


Since the OrderedPostProcessor handles calling postProcessBeanFactory() on my other post processors, I no longer need to define those beans as post processors inside my ColdSpring.xml, leaving me with the following:

ColdSpring.xml

<beans default-autowire="no">

<bean id="beanDetector" class="com.util.BeanDetector">
<property name="directories">
<list>
<value>/com/app/controllers/</value>
<value>/com/app/model/</value>
<value>/com/app/services/</value>
</list>
</property>
<property name="patterns">
<list>
<value>[\w]+Controller</value>
<value>[\w]+Gateway</value>
<value>[\w]+Service</value>
</list>
</property>
</bean>

<bean id="beansScopeInjector" class="com.util.BeansScopePostProcessor" />

<bean id="helpers" class="com.util.HelpersScopePostProcessor">
<property name="directories">
<list>
<value>/com/app/helpers/</value>
</list>
</property>
</bean>

<bean id="orderedPostProcessor" class="com.util.OrderedPostProcessor" factory-post-processor="true">
<property name="postProcessors">
<list>
<ref bean="beanDetector" />
<ref bean="beansScopeInjector" />
<ref bean="helpers" />
</list>
</property>
</bean>

</beans>


And finally, here's the code:

OrderedPostProcessor.cfc

component accessors="true" {

property postProcessors;

public void function postProcessBeanFactory(required any beanFactory) {

var i = "";

for (i=1; i <= arrayLen(variables.postProcessors); i++) {
variables.postProcessors[i].postProcessBeanFactory(arguments.beanFactory);
}

}

}


Sorry for such a long post. If you took the time to read the whole thing, hopefully it was worth it.

1 comment:

Note: Only a member of this blog may post a comment.