I recently stumbled upon a couple ColdFusion wiki sites that I thought were worth mentioning.
Both sites were created by Kevan Stannard, a ColdFusion developer from Australia. While the sites appear to be a work in progress, there's still quite a bit of useful, thorough, and user-friendly content to read, as well as links to other resources on the web. His personal development blog has some good posts that are worth checking out too.
I don't know Kevan and honestly I hadn't heard of him until running across his sites, which I find a little surprising considering the quality of the content. It was pretty refreshing to see some common programming techniques applied in ColdFusion and explained with a human touch. I wish I had found these sites when I was first starting out.
Anyways, I just thought he should get a quick shout out for his efforts.
Tuesday, December 1, 2009
Wednesday, November 11, 2009
ORM Event Handling in ColdFusion 9
I've been playing around with ORM event handling in ColdFusion 9. For my application, I want to be able to fire events during various points in the lifecyle of my persistent entities. However, I don't want to have to clutter up my business logic with tasks such as logging, synchronizing integration data, or pushing changes out to Flex. I want to take a more AOP-style approach.
To do this, I created an XML-driven EventManager to use as my application's global event handler. With my EventManager, I'm able to configure listeners to respond to events fired by my entities using the built in ORM event handlers. The events can be registered using regular expressions and all follow the pattern {EntityName}:{EventHandler}. For example, I might have User:postUpdate or Product:postDelete.
In my sample application, I've configured my EventManager to send an email before and after any entity is loaded, inserted, updated, or deleted. In other words, basically anytime something happens to an entity. I'm not sure how useful this would be, but it's just an example.
Here's the directory structure:

Application.cfc
EventHandler.cfc
EventManager.cfc
NotficationService.cfc
coldspring.xml
events.xml
I'm not sure if this is the best approach or how well this would scale, but it seems to work pretty well for now.
PS - note the use of savecontent inside script. And they said it was pointless... :)
To do this, I created an XML-driven EventManager to use as my application's global event handler. With my EventManager, I'm able to configure listeners to respond to events fired by my entities using the built in ORM event handlers. The events can be registered using regular expressions and all follow the pattern {EntityName}:{EventHandler}. For example, I might have User:postUpdate or Product:postDelete.
In my sample application, I've configured my EventManager to send an email before and after any entity is loaded, inserted, updated, or deleted. In other words, basically anytime something happens to an entity. I'm not sure how useful this would be, but it's just an example.
Here's the directory structure:

Application.cfc
component {
this.name = "Sample";
this.datasource = "Sample";
this.mappings["/sample"] = getDirectoryFromPath(getCurrentTemplatePath());
this.ormEnabled = true;
this.ormSettings.dbcreate = "update";
this.ormSettings.eventHandling = true;
this.ormSettings.eventHandler = "sample.com.EventHandler";
public void function onApplicationStart() {
application.beanFactory = createObject("component","coldspring.beans.DefaultXmlBeanFactory").init();
application.beanFactory.loadBeans("/sample/config/coldspring.xml");
}
}
EventHandler.cfc
component implements="cfide.orm.IEventHandler" {
public void function preLoad(any entity) {
handleEvent(entity,"preLoad");
}
public void function postLoad(any entity) {
handleEvent(entity,"postLoad");
}
public void function preInsert(any entity) {
handleEvent(entity,"preInsert");
}
public void function postInsert(any entity) {
handleEvent(entity,"postInsert");
}
public void function preUpdate(any entity, struct oldData) {
handleEvent(entity,"preUpdate");
}
public void function postUpdate(any entity) {
handleEvent(entity,"postUpdate");
}
public void function preDelete(any entity) {
handleEvent(entity,"preDelete");
}
public void function postDelete(any entity) {
handleEvent(entity,"postDelete");
}
private void function handleEvent(any entity, string handler) {
var collection = {};
collection.entity = ormGetSession().getEntityName(entity);
collection.id = entity.getID();
collection.handler = handler;
var eventManager = application.beanFactory.getBean("eventManager");
eventManager.dispatchEvent("#collection.entity#:#collection.handler#",collection);
}
}
EventManager.cfc
component accessors="true" {
property configPath;
public any function init() {
variables.events = {};
variables.loaded = false;
}
public void function setConfigPath(required string configPath) {
if(fileExists(configPath)) {
variables.configPath = configPath;
}
else {
variables.configPath = expandPath(configPath);
}
}
public void function dispatchEvent(required string event, struct data) {
if(!structKeyExists(arguments,"data")) {
arguments.data = {};
}
if(!variables.loaded) {
loadConfig();
variables.loaded = true;
}
local.listeners = getListeners(event);
for (var i=1;i <= arrayLen(local.listeners);i++) {
local.bean = application.beanFactory.getBean(local.listeners[i].bean);
evaluate("local.bean.#local.listeners[i].method#(argumentCollection=data)");
}
}
private array function getListeners(required string event) {
if(!structKeyExists(variables.events,event)) {
local.used = {};
local.listeners = [];
for (var i=1;i <= arrayLen(variables.config);i++) {
if(reFindNoCase(variables.config[i].name,event)) {
for (var j=1;j <= arrayLen(variables.config[i].listeners);j++) {
local.listener = variables.config[i].listeners[j];
if(!structKeyExists(local.used,local.listener.id)) {
arrayAppend(local.listeners,local.listener);
local.used[local.listener.id] = true;
}
}
}
}
variables.events[event] = local.listeners;
}
return variables.events[event];
}
private void function loadConfig() {
variables.config = [];
local.xml = xmlParse(fileRead(getConfigPath()));
for (var i=1;i <= arrayLen(local.xml.events.xmlChildren);i++) {
local.event = {};
local.event.name = local.xml.events.xmlChildren[i].xmlAttributes.name;
local.event.listeners = [];
for (var j=1;j <= arrayLen(local.xml.events.xmlChildren[i].xmlChildren);j++) {
local.listener = {};
local.listener.bean = local.xml.events.xmlChildren[i].xmlChildren[j].xmlAttributes.bean;
local.listener.method = local.xml.events.xmlChildren[i].xmlChildren[j].xmlAttributes.method;
local.listener.id = local.listener.bean & "." & local.listener.method;
arrayAppend(local.event.listeners,local.listener);
}
arrayAppend(variables.config,local.event);
}
}
}
NotficationService.cfc
component {
public void function sendNotification() {
savecontent variable="local.body" {
writeDump(arguments)
}
var notification = new Mail();
notification.setTo("joe@example.com");
notification.setFrom("joe@example.com");
notification.setSubject("Notification");
notification.setType("html");
notification.send(body=local.body);
}
}
coldspring.xml
<beans>
<bean id="eventManager" class="sample.com.EventManager">
<property name="configPath">
<value>/sample/config/events.xml</value>
</property>
</bean>
<bean id="notificationService" class="sample.com.NotificationService" />
</beans>
events.xml
<events>
<event name="[\w]+:(pre|post)(Load|Insert|Update|Delete)">
<listener bean="notificationService" method="sendNotification" />
</event>
</events>
I'm not sure if this is the best approach or how well this would scale, but it seems to work pretty well for now.
PS - note the use of savecontent inside script. And they said it was pointless... :)
Tuesday, November 10, 2009
ColdFusion 9 Mail in cfscript
I ran into an issue today trying to send an email using script syntax. My code was pretty simple:
However, when I tried running the code, I got the following error:
Could not find the ColdFusion component or interface Mail.
While it's pretty obvious now what the problem is now, at first I was pretty confused. I figured since all the tags were converted to handle script syntax, everything should just work. I checked the ColdFusion 9 documentation and everything looked fine. After about 10 minutes I finally figured it out.
When installing ColdFusion, one of the first things I do is clean up ColdFusion Administrator by removing the default datasources and custom tag paths. However, when Adobe added script support for a couple tags (ftp, http, mail, pdf, query, storedproc), they chose to implement the functions as objects using CFCs. When I deleted the default custom tag path, ColdFusion was no longer able to find the Mail.cfc, since it was relying on the custom tag path.
If you go to cf_root\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\cfusion\CustomTags\com\adobe\coldfusion\, you should see all the tags implemented as CFCs. Kinda interesting.
On a random note, although it was said that <cfsavecontent /> would not be implemented in script syntax, apparently it was. You can see it in action if you view the examples on the documentation for using mail in script.
var email = new Mail();
email.setTo("joe@example.com");
email.setFrom("joe@example.com");
email.setSubject("Test Email");
email.setType("html");
email.send(body="Hello, world");
However, when I tried running the code, I got the following error:
Could not find the ColdFusion component or interface Mail.
While it's pretty obvious now what the problem is now, at first I was pretty confused. I figured since all the tags were converted to handle script syntax, everything should just work. I checked the ColdFusion 9 documentation and everything looked fine. After about 10 minutes I finally figured it out.
When installing ColdFusion, one of the first things I do is clean up ColdFusion Administrator by removing the default datasources and custom tag paths. However, when Adobe added script support for a couple tags (ftp, http, mail, pdf, query, storedproc), they chose to implement the functions as objects using CFCs. When I deleted the default custom tag path, ColdFusion was no longer able to find the Mail.cfc, since it was relying on the custom tag path.
If you go to cf_root\servers\cfusion\cfusion-ear\cfusion-war\WEB-INF\cfusion\CustomTags\com\adobe\coldfusion\, you should see all the tags implemented as CFCs. Kinda interesting.
On a random note, although it was said that <cfsavecontent /> would not be implemented in script syntax, apparently it was. You can see it in action if you view the examples on the documentation for using mail in script.
savecontent variable="mailBody"{
WriteOutput("This message was sent by...");
}
Thursday, October 29, 2009
ColdFusion + Spring?
After the recent additions of Hibernate, Ehcache and Solr (Lucene) to ColdFusion 9, it's starting to become really clear what ColdFusion does best: provide a clean and easy interface to powerful, open-source Java libraries.
With that being said, I would vote for Spring to be included next. While ColdSpring is great, why not go straight to the source? Not only would it allow for a tighter, seemless integration between ColdFusion and Java objects, but it would also leverage the power and performance of Java for our IoC and AOP needs. On top of that, Spring provides other services, such as integration with BlazeDS for Flex powered applications.
Just think, you could configure Spring inside your Application.cfc using this.iocEnabled and this.iocSettings.iocConfig. And if you didn't want to use XML, you could define your beans using annotations in your components (component beanid="userService" scope="singleton") and wire in your dependencies through properties (property name="userService" autowire="true";). It could look pretty similar to the way Hibernate was implemented.
Granted trying to integrate Spring into ColdFusion is probably easier said than done, but if they got it to work with Hibernate, I'm sure it could be done with Spring too.
With that being said, I would vote for Spring to be included next. While ColdSpring is great, why not go straight to the source? Not only would it allow for a tighter, seemless integration between ColdFusion and Java objects, but it would also leverage the power and performance of Java for our IoC and AOP needs. On top of that, Spring provides other services, such as integration with BlazeDS for Flex powered applications.
Just think, you could configure Spring inside your Application.cfc using this.iocEnabled and this.iocSettings.iocConfig. And if you didn't want to use XML, you could define your beans using annotations in your components (component beanid="userService" scope="singleton") and wire in your dependencies through properties (property name="userService" autowire="true";). It could look pretty similar to the way Hibernate was implemented.
Granted trying to integrate Spring into ColdFusion is probably easier said than done, but if they got it to work with Hibernate, I'm sure it could be done with Spring too.
Thursday, October 22, 2009
ColdFusion 9 ORM Custom Naming Strategy
I'm pretty particular when it comes to naming conventions. If I'm working with code and objects, I prefer properties to be in written in camel case. However, I like database fields to be written in proper case, with all spaces replaced with underscores. For example, I would have user.id and user.firstName in my code, but User.ID and User.First_Name in my database.
Hibernate is able to automatically generate your database schema for you based on your *.hbmxml files, which can be automatically generated for you by ColdFusion after creating your Entity CFCs. While this can be a really handy feature for development, I was a little annoyed when Hibernate generated all of my columns in camel case to match my properties.
Then I found a really nice post by an Adobe engineer that talked about using a custom naming strategy, which is a component that Hibernate can use to generate your database table and column names.
In your Application.cfc, you can set this.ormSettings.namingStrategy to a CFC that implements the "cfide.orm.INamingStrategy" interface.
cfide.orm.INamingStrategy
With that in mind, here's my UncamelizeStrategy.cfc
I'm sure there's probably an easier way to write this, perhaps using a regular expression, but I chose the brute-force approach of looping over each character. Short and simple.
Hibernate is able to automatically generate your database schema for you based on your *.hbmxml files, which can be automatically generated for you by ColdFusion after creating your Entity CFCs. While this can be a really handy feature for development, I was a little annoyed when Hibernate generated all of my columns in camel case to match my properties.
Then I found a really nice post by an Adobe engineer that talked about using a custom naming strategy, which is a component that Hibernate can use to generate your database table and column names.
In your Application.cfc, you can set this.ormSettings.namingStrategy to a CFC that implements the "cfide.orm.INamingStrategy" interface.
cfide.orm.INamingStrategy
component {
public string function getTableName(string tableName) {
}
public string function getColumnName(string columnName) {
}
}
With that in mind, here's my UncamelizeStrategy.cfc
component implements="cfide.orm.INamingStrategy" {
public string function getTableName(string tableName) {
return uncamelize(tableName);
}
public string function getColumnName(string columnName) {
return uncamelize(columnName);
}
private string function uncamelize(string name) {
var array = [];
for (var i=1;i <= len(name);i++) {
var char = mid(name,i,1);
if(i == 1) {
arrayAppend(array,ucase(char));
}
else {
if(reFind("[A-Z]",char)) {
arrayAppend(array,"_" & char);
}
else {
arrayAppend(array,lcase(char));
}
}
}
var newName = arrayToList(array,"");
if(newName == "Id") {
newName = "ID";
}
else if(right(newName,3) == "_Id") {
newName = left(newName,len(newName)-3) & "_ID";
}
return newName;
}
}
I'm sure there's probably an easier way to write this, perhaps using a regular expression, but I chose the brute-force approach of looping over each character. Short and simple.
Subscribe to:
Posts (Atom)
