Tuesday, January 12, 2010

Consistent Looping in ColdFusion

I've said this before, but I hate how inconsistent looping is in ColdFusion. And to make matters worse, ColdFusion 9 script syntax doesn't support looping over arrays using for...in statements. Why not?

I decided to try to ease my pain by creating a custom tag to loop over data in a consistent way. Here's what I came up with.

Usage

<cfimport prefix="for" taglib="/com/tags" />

<for:each key="key" in="#data#" value="value" index="i">
... stuff ...
</for:each>


Examples

<cfimport prefix="for" taglib="/com/tags" />

<cfoutput>
Array

<cfset people = [] />
<cfset people[1] = "LeBron James" />
<cfset people[2] = "Dwyane Wade" />
<cfset people[3] = "Kobe Bryant" />

<for:each in="#people#">
Hello, my name is #it#
</for:each>

Struct

<cfset people = {} />
<cfset people["LeBronJames"] = "LeBron James" />
<cfset people["DwyaneWade"] = "Dwyane Wade" />
<cfset people["KobeBryant"] = "Kobe Bryant" />

<for:each in="#people#" value="name">
Hello, my name is #name#
</for:each>

List

<cfset people = "LeBron James,Dwyane Wade,Kobe Bryant" />

<for:each in="#people#">
Hello, my name is #it#
</for:each>

Query

<cfset people = queryNew("firstName,lastName") />

<cfset queryAddRow(people) />
<cfset querySetCell(people, "firstName", "LeBron") />
<cfset querySetCell(people, "lastName", "James") />

<cfset queryAddRow(people) />
<cfset querySetCell(people, "firstName", "Dwyane") />
<cfset querySetCell(people, "lastName", "Wade") />

<cfset queryAddRow(people) />
<cfset querySetCell(people, "firstName", "Kobe") />
<cfset querySetCell(people, "lastName", "Bryant") />

<for:each in="#people#" value="person">
Hello, my name is #person.firstName# #person.lastName#
</for:each>

Nested Array

<cfset people = [] />
<cfset people[1] = {firstName="LeBron", lastName="James"} />
<cfset people[2] = {firstName="Dwyane", lastName="Wade"} />
<cfset people[3] = {firstName="Kobe", lastName="Bryant"} />

<for:each in="#people#" value="person">
Hello, my name is #person.firstName# #person.lastName#
</for:each>

Nested Struct

<cfset people = {} />

<cfset people["LeBronJames"] = {firstName="LeBron", lastName="James"} />
<cfset people["DwyaneWade"] = {firstName="Dwyane", lastName="Wade"} />
<cfset people["KobeBryant"] = {firstName="Kobe", lastName="Bryant"} />

<for:each in="#people#" value="person">
Hello, my name is #person.firstName# #person.lastName#
</for:each>
</cfoutput>


And here's the code
/com/tags/for/each.cfm

<cfif thisTag.executionMode eq "start">

<cfparam name="attributes.value" default="it" />
<cfparam name="attributes.in" default="" />
<cfparam name="attributes.start" default="1" />
<cfparam name="attributes.delimeter" default="," />

<cfset attributes.type = getType(attributes.in) />
<cfset attributes.length = getLength(attributes.in, attributes.type, attributes.delimeter) />

<cfif not structKeyExists(attributes, "end")>
<cfset attributes.end = attributes.length />
</cfif>

<cfif attributes.length>
<cfset processLoop(attributes) />
<cfelse>
<cfexit method="exittag" />
</cfif>

<cfset content = [] />

<cfelse>

<cfset arrayAppend(content, thisTag.generatedContent) />

<cfset thisTag.generatedContent = "" />

<cfset attributes.start++ />

<cfif attributes.start lte attributes.end>
<cfset processLoop(attributes) />
<cfexit method="loop" />
</cfif>

<cfoutput>
#arrayToList(content, "")#
</cfoutput>

</cfif>

<cffunction name="processLoop" access="private" output="false" returntype="void">
<cfargument name="attributes" required="true" type="struct" />

<cfif structKeyExists(attributes, "index")>
<cfset caller[attributes.index] = attributes.start />
</cfif>

<cfif structKeyExists(attributes, "key")>
<cfset caller[attributes.key] = getKey(attributes.in, attributes.type, attributes.delimeter, attributes.start) />
</cfif>

<cfif structKeyExists(attributes, "value")>
<cfset caller[attributes.value] = getValue(attributes.in, attributes.type, attributes.delimeter, attributes.start) />
</cfif>

</cffunction>

<cffunction name="getType" access="private" output="false" returntype="string">
<cfargument name="data" required="true" type="any" />

<cfif isArray(arguments.data)>
<cfreturn "array" />
<cfelseif isStruct(arguments.data)>
<cfreturn "struct" />
<cfelseif isQuery(arguments.data)>
<cfreturn "query" />
<cfelse>
<cfreturn "string" />
</cfif>

</cffunction>

<cffunction name="getLength" access="private" output="false" returntype="numeric">
<cfargument name="data" required="true" type="any" />
<cfargument name="type" required="true" type="string" />
<cfargument name="delimeter" required="true" type="string" />

<cfswitch expression="#arguments.type#">

<cfcase value="array">
<cfreturn arrayLen(arguments.data) />
</cfcase>

<cfcase value="struct">
<cfreturn structCount(arguments.data) />
</cfcase>

<cfcase value="query">
<cfreturn arguments.data.recordCount />
</cfcase>

<cfcase value="string">
<cfreturn listLen(arguments.data, arguments.delimeter) />
</cfcase>

</cfswitch>

</cffunction>

<cffunction name="getKey" access="private" output="false" returntype="string">
<cfargument name="data" required="true" type="any" />
<cfargument name="type" required="true" type="string" />
<cfargument name="delimeter" required="true" type="string" />
<cfargument name="index" required="true" type="numeric" />

<cfset var result = "" />

<cfset var i = "" />

<cfswitch expression="#arguments.type#">

<cfcase value="array">
<cfset result = arguments.index />
</cfcase>

<cfcase value="struct">
<cfset result = listGetAt(listSort(structKeyList(arguments.data), "text"), arguments.index) />
</cfcase>

<cfcase value="query">
<cfset result = arguments.index />
</cfcase>

<cfcase value="string">
<cfset result = listGetAt(arguments.data, arguments.index, arguments.delimeter) />
</cfcase>

</cfswitch>

<cfreturn result />

</cffunction>

<cffunction name="getValue" access="private" output="false" returntype="any">
<cfargument name="data" required="true" type="any" />
<cfargument name="type" required="true" type="string" />
<cfargument name="delimeter" required="true" type="string" />
<cfargument name="index" required="true" type="numeric" />

<cfset var result = "" />
<cfset var i = "" />

<cfswitch expression="#arguments.type#">

<cfcase value="array">
<cfset result = arguments.data[arguments.index] />
</cfcase>

<cfcase value="struct">
<cfset result = arguments.data[listGetAt(listSort(structKeyList(arguments.data), "text"), arguments.index)] />
</cfcase>

<cfcase value="query">
<cfset result = {} />
<cfloop list="#arguments.data.columnList#" index="i">
<cfset result[i] = arguments.data[i][arguments.index] />
</cfloop>
</cfcase>

<cfcase value="string">
<cfset result = listGetAt(arguments.data, arguments.index, arguments.delimeter) />
</cfcase>

</cfswitch>

<cfreturn result />

</cffunction>


It's been quite a while since I've used custom tags, so the code could probably be better, but it seems to do the job.

Thursday, January 7, 2010

Using a ColdSpring Post Processor to add "Static" Domain Classes

This is a quick follow-up to an idea I had created by one of Dan Vega's latest posts.

If your application has Products, you might have a ProductController that looks something like this:


component {

property productService;

public array function list() {
return getProductService().listProducts();
}

}


Then you might have a ProductService that might look like:


component {

property dao;

public array function listProducts() {
return getDAO().list("Product");
}

}


And finally you might have a generic DAO that looks like this:


component {

public array function list(string entityName) {
return entityLoad(entityName);
}

}


And now you've got proper encapsulation and separation of concerns with loosely couple components, which is good. However, you also have several components that merely delegate to other components.

In Grails and Rails, you can access your data through static methods on your domain classes. So rather than having the full stack of components, you could simply call Product.list() inside your controller.

I wanted to do something similar in ColdFusion.

First, I added a DomainClassInjector factory post processor to ColdSpring. It accepts an array of suffixes as well as the path to a generic domain class.


<bean id="domainClassInjector" class="com.utils.DomainClassInjector" factory-post-processor="true">
<property name="suffixes">
<list>
<value>Controller</value>
</list>
</property>
<property name="classPath">
<value>com.utils.DomainClass</value>
</property>
</bean>


Here's the DomainClassInjector.cfc

component accessors="true" {

property suffixes;
property classPath;

public any function init() {

variables.domainClasses = {};
variables.entityNames = ormGetSessionFactory().getAllClassMetaData();

return this;

}

public void function postProcessBeanFactory(required any beanFactory) {

var i = "";

local.beanDefinitions = arguments.beanFactory.getBeanDefinitionList();

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

local.suffix = variables.suffixes[i];

local.length = len(local.suffix);

for (local.beanName in local.beanDefinitions) {

if (right(local.beanName, local.length) == local.suffix) {

local.bean = arguments.beanFactory.getBean(local.beanName);

for (local.entityName in variables.entityNames) {

if (structKeyExists(local.bean, "set#local.entityName#")) {

if (structKeyExists(variables.domainClasses, local.entityName)) {
local.domainClass = variables.domainClasses[local.entityName];
}
else {
local.domainClass = createObject("component", variables.classPath);
local.domainClass.setEntityName(local.entityName);
variables.domainClasses[local.entityName] = local.domainClass;
}

evaluate("local.bean.set#local.entityName#(local.domainClass)");

}

}

}

}

}

}

}


And here's the DomainClass.cfc

component accessors="true" {

property entityName;

public any function new() {
return entityNew(getEntityName());
}

public any function load(required string id) {
return entityLoadByPK(getEntityName(), arguments.id);
}

public array function list() {
return entityLoad(getEntityName());
}

public any function onMissingMethod(required string missingMethodName, required struct missingMethodArguments) {
// logic for dynamic finders...
}

}


Now I can inject any domain classes I want into my controllers by adding a property that matches the name of my domain class. Here's what my ProductController looks like now:


component accessors="true" {

property Product;

public array function list() {
return Product.list();
}

}


I'm not sure if I'm ready to abandon the Controller/Service/DAO stack yet, but it's nice to know I have options. If I were to fully embrace this approach, I would probably have my entities all extend the DomainClass.cfc, then have the DomainClassInjector inject new instances of my entities into my controllers.

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.