Archive for the ‘ColdFusion’ Category

Using JavaLoader in Mach-II

Sunday, February 14th, 2010

There are probably dozens of ways to incorporate Mark Mandel’s JavaLoader library into Mach-II, but the way I’m going to focus on today involves writing a custom Property to handle configuration and access.

First off, let’s look at the source code for the Property:

<cfcomponent
   displayname="JavaLoaderProperty"
   output="false"
   extends="MachII.framework.Property">
 
  <cfset variables.loadPaths = ArrayNew(1)/>
  <cfset variables.javaLoader = ""/>
 
  <cffunction name="configure" access="public" output="false" returntype="void"
              hint="Called automatically by Mach-II on application initialization">
    <cfif isParameterDefined("loadPaths")>
      <cfset setLoadPaths(getParameter("loadPaths"))/>
    </cfif>
 
    <cfset prepareJavaLoader()/>
  </cffunction>
 
  <cffunction name="getJavaLoader" access="public" output="false" returntype="javaloader.JavaLoader">
    <cfreturn variables.javaLoader/>
  </cffunction>
 
  <cffunction name="prepareJavaLoader" access="private" output="false" returntype="void">
    <cfset var pathArray = ArrayNew(1)/>
    <cfset var i = 0/>
    <cfloop from="1" to="#ArrayLen(variables.loadPaths)#" index="i">
      <cfset pathArray[i] = expandPath(variables.loadPaths[i])/>
    </cfloop>
    <cfset variables.javaLoader = createObject("component", "javaloader.JavaLoader").init(pathArray)/>
  </cffunction>
 
  <cffunction name="setLoadPaths" access="public" output="false" returntype="void">
    <cfargument name="loadPaths" type="array" required="true"/>
    <cfset variables.loadPaths = arguments.loadPaths/>
  </cffunction>
  <cffunction name="getLoadPaths" access="public" output="false" returntype="array">
    <cfreturn variables.loadPaths/>
  </cffunction>
 
 
</cfcomponent>

The first thing you’ll notice is that this is a very simple Property. It only has five methods, 3 of which are accessors/mutators.

The configuration method, per Mach-II Property convention, accesses configured properties through the ‘isParameterDefined/getParameter’ methods. These are convenience methods for pulling configuration data out of the mach-ii.xml file.

Speaking of which, let’s take a look at the configuration of this particular parameter within the mach-ii.xml file:

    <property name="javaLoader" type="path.to.JavaLoaderProperty">
      <parameters>
	<parameter name="loadPaths">
	  <array>
	    <element value="lib/core.jar"/>
	    <element value="lib/javase.jar"/>
	    <element value="lib/jai_codec.jar"/>
	    <element value="lib/jai_core.jar"/>
	    <element value="lib/barbecue.jar"/>
	    <element value="lib/PDFRenderer.jar"/>
	  </array>
	</parameter>
      </parameters>
    </property>

The most important thing to notice is that the configuration parameter is called ‘loadPaths’, which is exactly what we were trying to access from the configuration method in the Property class.

Once the Property is configured, it sits in the PropertyManager and waits for Listeners to call it via the getProperty function, like so:

  <cffunction name="prepareBarcode" access="public" returntype="void" output="false">
    <cfargument name="event" type="MachII.framework.Event" required="yes"/>
 
    <cfset var javaLoader = getProperty("javaLoader").getJavaLoader()/>
    <cfset var barcodeFactory = javaLoader.create("net.sourceforge.barbecue.BarcodeFactory")/>
     . . .
  </cffunction>

Very simply, we access the javaLoader Property via getProperty("javaLoader").getJavaLoader(). Once we have that, we can load any Class that is contained within the Jar files we used during the Property configuration.

I hope this method of accessing a JavaLoader property is useful. If there are any questions or comments, please feel free to leave them below.

Implementing a Custom Logger in Mach-II, Part 3

Wednesday, November 18th, 2009

This is the last of a three-part series. For the first and second parts, click here and here.

When we left off last time we had a nearly functioning logger; we were just missing the bits that actually output the logging information to IRC. Let’s jump right in and fill out the onRequestEnd callback and get those logs dumped correctly:

  <cffunction name="onRequestEnd" access="public" returntype="void" output="false"
	      hint="Sends an email for this logger.">
 
    <cfset var data = ArrayNew(1) />
    <cfset var messageString = "" />
    <cfset var i = 0/>
 
    <!--- Only display output if logging is enabled --->
    <cfif getLogAdapter().getLoggingEnabled() AND getLogAdapter().isLoggingDataDefined()>
 
      <cfset data = getLogAdapter().getLoggingData().data />
 
      <cfif ArrayLen(data)>
	<!--- Send to the channel --->
	<cfloop index="i" from="1" to="#ArrayLen(data)#">
	  <cfset messageString = javaCast("string", data[i].channel & ": " & data[i].message)/>
	  <cfset sendMessage(messageString)/>
	</cfloop>
      </cfif>
    </cfif>
  </cffunction>
 
  <cffunction name="sendMessage" access="private" returntype="void" output="false">
    <cfargument name="message" type="string" required="true"/>
    <cfset var messageCommand = createObject("java", "f00f.net.irc.martyr.commands.MessageCommand").init(getChannel(), arguments.message)/>
    <cfset getBot().sendCommand(messageCommand)/>
  </cffunction>

A very straightforward callback. A vast majority of the time your onRequestEnd callbacks will look exactly like this one, with the only difference being what’s within the cfloop. The method simply checks to see whether logging is enabled, and if so it loops over all the logging data available, sending each line to the IRC connection through sendMessage. If you want to see what values are available to play around with in the data array, check out the MachIILog’s output template in the source code. It’s a simple page that’s pretty easy to understand.

And with that, we have a functioning logger. Let’s look at the configuration in mach-ii.xml:

    <property name="Logging" type="MachII.logging.LoggingProperty">
      <parameters>
	<parameter name="IrcLog">
	  <struct>
	    <key name="type" value="project.loggers.IrcLogger" />
	    <key name="loggingEnabled" value="true" />
	    <key name="loggingLevel" value="info" />
	    <key name="server" value="irc.server.net"/>
	    <key name="channel" value="#debugging"/>
	    <key name="nick" value="project[bot]"/>
	    <key name="filter" value="*,!MachII*"/>
	  </struct>
	</parameter>
      </parameters>
    </property>

Notice server, channel, and nick? Those were the parameters we configured in Part 2. This is where we give those parameters values. Type points to the logger CFC we’ve been writing, and filter sets up some of the filtering rules mentioned in Part 2.

For more on filtering, read how to use the GenericChannelFilter on the wiki; if you want to set up multiple loggers, check out the multiple logger section on the same page.

Okay, we’re set up and running, but we’re not handling redirects yet. Redirects simply require that the logging data accrued prior to the redirect be persisted and then restored into the new logging data once the redirect is finished. This requires we fill out preRedirect, postRedirect, and a convenience method called arrayConcat:

  <cffunction name="preRedirect" access="public" returntype="void" output="false"
	      hint="Pre-redirect logic for this logger.">
    <cfargument name="data" type="struct" required="true"
		hint="Redirect persist data struct." />
 
    <cfif getLogAdapter().getLoggingEnabled() AND getLogAdapter().isLoggingDataDefined()>
      <cfset arguments.data[getLoggerId()] = getLogAdapter().getLoggingData() />
    </cfif>
  </cffunction>
 
  <cffunction name="postRedirect" access="public" returntype="void" output="false"
	      hint="Post-redirect logic for this logger.">
    <cfargument name="data" type="struct" required="true"
		hint="Redirect persist data struct." />
 
    <cfset var loggingData = StructNew() />
 
    <cfif getLogAdapter().getLoggingEnabled() AND getLogAdapter().isLoggingDataDefined()>
      <cftry>
	<cfset loggingData = getLogAdapter().getLoggingData() />
	<cfset loggingData.data = arrayConcat(arguments.data[getLoggerId()].data, loggingData.data) />
	<cfcatch type="any">
	  <!--- Do nothing as the configuration may have changed between start of
	      the redirect and now --->
	</cfcatch>
      </cftry>
    </cfif>
  </cffunction>
 
  <cffunction name="arrayConcat" access="private" returntype="array" output="false"
	      hint="Concats two arrays together.">
    <cfargument name="array1" type="array" required="true" />
    <cfargument name="array2" type="array" required="true" />
 
    <cfset var result = arguments.array1 />
    <cfset var i = 0 />
 
    <cfloop from="1" to="#ArrayLen(arguments.array2)#" index="i">
      <cfset ArrayAppend(result, arguments.array2[i]) />
    </cfloop>
 
    <cfreturn result />
  </cffunction>

There’s a lot going on here, but don’t panic! Most of the time you’ll be able to copy/paste these methods directly into your logger and it will just work. The only time that might not be true is if you implement your own custom Adapter. Functionally, both methods check to make sure logging is enabled, and then either persist or restore the logging data through the registered logging adapter by assigning or saving the data from a data array passed in as an argument.

There’s one more method before we wrap up this little tutorial: getConfigurationData. As far as I know, this method is just a way to let the Dashboard module (which if you’re not using, you should really look into) know some information about your logger. For this logger, I implemented it like so:

  <cffunction name="getConfigurationData" access="public" returntype="struct" output="false"
	      hint="Gets the configuration data for this logger including adapter and filter.">
 
    <cfset var data = StructNew() />
 
    <cfset data["Server"] = getServer() />
    <cfset data["Channel"] = getChannel() />
    <cfset data["Nick"] = getNick() />
    <cfset data["Logging Enabled"] = YesNoFormat(isLoggingEnabled()) />
 
    <cfreturn data />
  </cffunction>

And that’s it! We now have a perfectly working IRC-based logger for the Mach-II framework. Finally I can just lounge in my favorite IRC channels and know instantly when a problem happens with my web applications. It’s also pretty handy to include a call to getLog().info() when some interesting function I wrote goes off, just to watch it run.

If you’d like to see the whole file, you can download it here.

As always, if you have questions or concerns, leave a comment. As always, I’m going to enjoy a post-entry glass of bourbon.

Implementing a Custom Logger in Mach-II, Part 2

Tuesday, November 17th, 2009

This is the second part of a three-part series. To read the first part, click here.

Let’s dive right in with some boring IRC housekeeping. Since this logger is an IRC-based logger, we’re going to have to store things like server and channel, so let’s make those properties of the object:

  <!---
      PROPERTIES
    --->
  <cfset variables.instance.loggerTypeName = "IRC" />
  <cfset variables.instance.server = "" />
  <cfset variables.instance.channel = "" />
  <cfset variables.instance.nick = "" />
  <cfset variables.instance.bot = "" />

In this case, server is the IRC server we’ll be connecting to; channel is the channel you want the logger to join; nick is the IRC nickname for the bot, and bot is the actual Java object that will be doing all the IRC trickery for us.

Unfortunately we have to do a little more in the way of boring housekeeping code: getters and setters. I’ll just copy/paste so we can knock these out and move to something more interesting.

  <!---
      ACCESSORS
    --->
  <cffunction name="setServer" access="private" returntype="void" output="false">
    <cfargument name="server" type="string" required="true" />
    <cfset variables.instance.server = arguments.server />
  </cffunction>
  <cffunction name="getServer" access="public" returntype="string" output="false">
    <cfreturn variables.instance.server />
  </cffunction>
 
  <cffunction name="setChannel" access="private" returntype="void" output="false">
    <cfargument name="channel" type="string" required="true" />
    <cfset variables.instance.channel = arguments.channel />
  </cffunction>
  <cffunction name="getChannel" access="public" returntype="string" output="false">
    <cfreturn variables.instance.channel />
  </cffunction>
 
  <cffunction name="setNick" access="private" returntype="void" output="false">
    <cfargument name="nick" type="string" required="true" />
    <cfset variables.instance.nick = arguments.nick />
  </cffunction>
  <cffunction name="getNick" access="public" returntype="string" output="false">
    <cfreturn variables.instance.nick />
  </cffunction>
 
  <cffunction name="setBot" access="private" returntype="void" output="false">
    <cfargument name="bot" type="any" required="true"/>
    <cfset variables.instance.bot = arguments.bot/>
  </cffunction>
  <cffunction name="getBot" access="private" returntype="any" output="false">
    <cfreturn variables.instance.bot/>
  </cffunction>

Now that these properties are in place, let’s dive into the configure method. First we need to set up the filter and the adapter.

The filter of a logger is simply a configurable object that filters out logging messages based on channels. Say you get along in your development of a Mach-II application and you decide you no longer want to see Mach-II debugging messages. You could disable the Mach-II Logger, or you could introduce a filter set to weed out all those debugging messages. I’ll get more into filter configuration in Part 3.

The adapter is the object you’ll eventually be using when you say getLog().debug("your error message"). That method call bubbles all the way into the adapter of the logging interface. The adapter handles all the actual logging of messages and stores all the logging data. We’ll be using these more in Part 3 when we handle redirects.

Let’s look at some code:

  <cffunction name="configure" access="public" returntype="void" output="false"
	      hint="Configures the logger.">
 
    <cfset var filter = CreateObject("component", "MachII.logging.filters.GenericChannelFilter").init(getParameter("filter", "")) />
    <cfset var adapter = CreateObject("component", "MachII.logging.adapters.ScopeAdapter").init(getParameters()) />
 
    <!--- Set the filter to the adapter --->
    <cfset adapter.setFilter(filter) />
 
    <!--- Configure and set the adapter --->
    <cfset adapter.configure() />
    <cfset setLogAdapter(adapter) />

This code simply sets up the filter and adapter. These default objects (GenericChannelFilter and ScopeAdapter) are probably what you want 99% of the time. If there’s enough demand I might write an entry about writing your own adapter or filter.

Let’s finish off the configure method by storing some of the preferences we’ll be passing to our logger via the mach-ii.xml config file and setting up the Java IRC connection object:

    <!--- Configure the remaining parameters --->
    <cfif isParameterDefined("server")>
      <cfset setServer(getParameter("server")) />
      <cfelse>
	<cfthrow type="IrcLogger"
		 message="A parameter named 'server' is required. The IRC server to which the logger will connect.">
    </cfif>
 
    <cfif isParameterDefined("channel")>
      <cfset setChannel(getParameter("channel")) />
      <cfelse>
	<cfthrow type="IrcLogger"
		 message="A parameter named 'channel' is required. The IRC channel which the logger will join.">
    </cfif>
 
    <cfif isParameterDefined("nick")>
      <cfset setNick(getParameter("nick")) />
      <cfelse>
	<cfthrow type="IrcLogger"
		 message="A parameter named 'nick' is required. The nick of the bot that will represent the logger.">
    </cfif>
 
    <cfset setBot(createObject("java", "f00f.net.irc.martyr.IRCConnection").init())/>
    <cfset createObject("java", "f00f.net.irc.martyr.services.AutoRegister").init(getBot(), getNick(), getNick(), getNick())/>
    <cfset createObject("java", "f00f.net.irc.martyr.services.AutoResponder").init(getBot())/>
    <cfset createObject("java", "f00f.net.irc.martyr.services.AutoJoin").init(getBot(), getChannel())/>
    <cfset createObject("java", "f00f.net.irc.martyr.services.AutoReconnect").init(getBot()).go(getServer(), 6667)/>
  </cffunction>

If you don’t quite follow all of the createObject lines toward the end, that’s perfectly fine. We’re simply setting up the Java object that will connect to the IRC server. Just note that the bot object property will point to the IRCConnection object, as we’ll need that for the deconfigure method.

Which we will fill out now. What does the deconfigure method do? It simply executes code that needs to happen when the logger is unloaded by the framework. In our case, we’d like for the IRCConnection to disconnect from the server and shut down. With that in mind, let’s write some code:

  <cffunction name="deconfigure" access="public" returntype="void" output="false">
    <cfset var quitCommand = createObject("java", "f00f.net.irc.martyr.commands.QuitCommand").init("Client exiting...")/>
    <cfset getBot().sendCommand(quitCommand)/>
    <cfset getBot().stop()/>
  </cffunction>

Pretty simple.

If you’re following along so far, what we have is a nearly functioning IRC logger. In the next part we’ll actually get to logging some messages, dealing with redirects, and configuring the logger to work with Mach-II. If you have any questions or corrections, please feel free to comment on this entry.

As for me, as always, it’s time for some bourbon.

Implementing a Custom Logger in Mach-II, Part 1

Monday, November 16th, 2009

Since I’m on IRC constantly anyway, I like the various web applications I maintain to be there with me, letting me know if there’s a problem, or when someone is using a feature I implemented — that sort of thing. Because of the integration between ColdFusion and Java, this hasn’t been such a hard thing to do historically. However, with recent changes to Mach-II’s logging framework, the implementation just got easier and cleaner.

Mach-II introduced Loggers in version 1.6 “to give developers a full blown logging package with extensibility to customize”, which is exactly what we’re going to take advantage of. If you’d like to read up on the MachII Logging framework, check out the wiki entry.

The first step is the Java library that will handle the IRC calls. I chose the martyr library because you don’t need to modify or extend it in order to use it for simple use cases, such as this one. If you’re feeling adventurous, you could use the more well known PircBot. I have used both; if you’re interested in getting a working logger, use martyr; if you eventually want to have a fully fledged IRC bot, try PircBot.

To install martyr, simply drop the file martyr.jar in your ColdFusion classpath (${ColdFusion Installation Dir}/lib).

Okay, let’s start coding! Fire up your favorite text editor and create the following skeleton:

<cfcomponent
   displayname="IRCLogger.Logger"
   extends="MachII.logging.loggers.AbstractLogger"
   output="false"
   hint="A logger for sending logging information to an IRC channel.">
 
  <!---
      PROPERTIES
    --->
  <cfset variables.instance.loggerTypeName = "IRC" />
 
  <!---
      INITIALIZATION / CONFIGURATION / DECONFIGURATION
    --->
  <cffunction name="configure" access="public" returntype="void" output="false"
	      hint="Configures the logger.">
 
  </cffunction>
 
  <cffunction name="deconfigure" access="public" returntype="void" output="false">
 
  </cffunction>
 
  <!---
      PUBLIC FUNCTIONS
    --->
  <cffunction name="onRequestEnd" access="public" returntype="void" output="false"
	      hint="Here the logger displays its data">
 
  </cffunction>
 
  <cffunction name="preRedirect" access="public" returntype="void" output="false"
	      hint="Pre-redirect logic for this logger.">
    <cfargument name="data" type="struct" required="true"
		hint="Redirect persist data struct." />
 
  </cffunction>
 
  <cffunction name="postRedirect" access="public" returntype="void" output="false"
	      hint="Post-redirect logic for this logger.">
    <cfargument name="data" type="struct" required="true"
		hint="Redirect persist data struct." />
 
  </cffunction>
 
  <cffunction name="getConfigurationData" access="public" returntype="struct" output="false"
	      hint="Gets the configuration data for this logger including adapter and filter.">
 
  </cffunction>
 
  <!---
      PROTECTED FUNCTIONS
    --->
 
 
  <!---
      ACCESSORS
    --->
 
</cfcomponent>

The configure and deconfigure methods are hooks that get called when the logger is being initialized by the framework and when the framework unloads the logger, respectively. You would want to put your startup/shutdown logic here. For our IRC example, these methods are where we will create and destroy the IRC connection object.

The onRequestEnd method is a callback that gets executed (surprise) at the end of the request lifecycle before the user’s page is rendered. This is where the logger renders its data into a cohesive output. In the Mach-II logger, this means outputing a nice HTML table structure with all the logging data gathered during the request lifecycle. For our IRC logger, that means taking all the logging information gathered during the request lifecyle and formatting it into an easy-to-read string (or strings) to be sent to an IRC channel.

The preRedirect and postRedirect functions are somewhat tricky. Conceptually they are easy to understand, but they delve the deepest into the Mach-II logging voodoo. They are at their core callbacks that happen before and after a redirect, respectively. During these callbacks you will want to persist and then restore all the logging information gathered thus far. If you’re confused at this prospect, I will expound on these functions in Part 3, so keep reading!

The getConfigurationData function is my favorite, because it’s the easiest to understand. Its job is simply to return information about this logger. I’m pretty sure that’s how the Mach-II Dashboard gets its nice logger display data.

Okay, we have our skeleton and we know roughly what’s going on. Part 2 is going to cover some boring housekeeping that’s necessary for IRC, then it will describe what we need to do to implement configure and deconfigure. Part 3 will be on the persisting and restoring of the logging data, and will wrap up our IRC logger with some Mach-II configuration code.

Now we’ll take a break for bourbon and regroup in the morning.

Using CKEditor with Mach-II and JQuery

Thursday, November 12th, 2009

CKEditor is a wonderful rich text editor written in Javascript. It more or less replaces a textarea with a robust WYSIWYG editor. Integrating it into your Mach-II application is extremely easy with the new Form taglib and HtmlHelperProperty. This article assumes you’ve already downloaded, extracted, and copied the CKEditor files into whichever directory you keep your Javascript files in.

First thing’s first: you’ll need to declare the HtmlHelperProperty in your mach-ii.xml file. In my case, the directory where I keep all my Javascript files is in the project directory, so I need to modify the jsBasePath parameter; the default will look in the absolute-referenced /js directory:

    <property name="html" type="MachII.properties.HtmlHelperProperty">
      <parameters>
	<parameter name="jsBasePath" value="js"/>
      </parameters>
    </property>

Once that’s in place, your application will be able to use all the cool functionality introduced with the HtmlHelperProperty. To learn more about this property, check out the specification on the Mach-II wiki.

The next step will be to make the form aware of the new Form taglib. This is easily accomplished by adding the following line to the top of your cfm file:

<cfimport prefix="form" taglib="/MachII/customtags/form" />

Again, feel free to refer to the documentation to learn more about the Form taglib.

Since we plan on using the CKEditor, we should probably import that Javascript file. I assume that most people use a templating scheme of some sort, so adding per-page Javascript files can get kind of hairy. Luckily the HtmlHelperProperty we declared earlier makes this extremely easy. Simply add the following line under the Form taglib declaration:

<cfset getProperty("html").addJavascript("jquery-1.3.2.min.js")/>
<cfset getProperty("html").addJavascript("ckeditor_basic.js")/>

I went ahead and threw in the JQuery declaration since we’ll be using that in a few minutes.

Now we need to define a textarea control that we’re going to replace with the CKEditor. In our test file, we’ll call this field description as if we’re describing some product in a bookstore.

  <form:form actionEvent="product.create" bind="product">
    <!--- . . . --->
    <form:textarea path="description" rows="10" cols="50"/>
     <!--- . . . --->
  </form:form>

So far, so good, and so simple. The next step is to actually add the Javascript code that replaces the textarea with the CKEditor control. The documentation suggests that you either do this directly after you create the textarea or put the code in function to be called when the page is finished loading. Fortunately JQuery makes this simple.

<script type="text/javascript">
var onLoadFunction = function() {
  CKEDITOR.replace("product");
};
 
$(document).ready(onLoadFunction);
</script>

All it takes is a couple dozen lines of code and your textarea can support rich text. Wow your friends, neighbors, and bosses, while you secretly let Mach-II do the heavy lifting, a glass of your favorite bourbon in hand. If you don’t like bourbon, I’ll hold your glass for you.

Mach-II Deployment with Apache Ant

Tuesday, November 10th, 2009

One of the (admittedly few) things that bugs me about ColdFusion development is deploying an application. Mach-II alleviated some of this pain with its recent addition of Environment properties, but (unless I’m mistaken) a large problem still exists: being able to deploy your application twice on the same machine or under a different directory name.

Why would you want to do this? Well, at my company we only have a development server and a deployment server. What should we do for staging, QA, or continuous integration deployments? We can fake it by deploying an application under a different name/directory on the same server as another deployment of the same application. We just have to make sure to avoid ApplicationName (and other property) collisions.

I do this with Apache Ant. First we create a Java properties file containing some per-deployment properties (I call this file build.properties):

cf.app.root=myproject
cf.app.dsn=mysql_myproject
cf.app.deploy.dir=/var/www/html/myproject

Then we modify the layout of the Mach-II Skeleton. For this example, all my config files will be put into the ‘config’ directory; Application.cfc is in the ‘base’ directory; all other deployment-agnostic files are in the ‘src’ directory.

Let’s take a look at how these mappings look in a basic mach-ii.xml:

<?xml version="1.0" encoding="UTF-8"?>
 
<!DOCTYPE mach-ii PUBLIC "-//Mach-II//DTD Mach-II Configuration 1.6.0//EN"
	  "http://www.mach-ii.com/dtds/mach-ii_1_6_0.dtd" >
 
<mach-ii version="1.6">
  <!-- INCLUDES -->
  <includes>
    <include file="./mach-ii_coldspringProperty.xml" />
  </includes>
 
  <properties>
    <property name="applicationRoot" value="/@cf.app.root@" />
    <property name="defaultEvent" value="home" />
    <property name="eventParameter" value="event" />
    <property name="parameterPrecedence" value="form" />
  </properties>
 
  <!-- LISTENERS -->
  <listeners>
    <listener name="testListener" type="/@cf.app.root@/listeners/TestListener"/>
    <listener name="userListener" type="/@cf.app.root@/listeners/UserListener"/>
    <listener name="reportListener" type="/@cf.app.root@/listeners/ReportListener"/>
  </listeners>
 
  <!-- . . . -->
</mach-ii>

Notice the @cf.app.root@? That tells Ant “We need to replace this with some kind of property value”. Now let’s look at a sample buildfile:

<?xml version="1.0"?>
<project name="myproject" default="dist">
  <description>Buildfile for a Sample Project</description>
 
  <target name="help" description="usage information for common tasks">
    <echo>usage: ant [target]</echo>
    <echo>target can be all, init, dist, deploy, clean, install, help </echo>
  </target>
 
  <target name="init" description="Initializes a project and all relevant data">
    <mkdir dir="build"/>
    <mkdir dir="build/config"/>
    <mkdir dir="dist"/>
  </target>
 
  <target name="clean" depends="init" description="Cleans up the entire project
						   (reset)">
    <delete dir="build"/>
    <delete dir="dist"/>
  </target>
 
  <target name="build" description="Builds the code from source files">
    <copy todir="build">
      <fileset dir="src"/>
      <fileset dir="webapp"/>
    </copy>
    <filter filtersfile="build.properties"/>
    <copy todir="build/config" filtering="true">
      <fileset dir="config"/>
    </copy>
    <copy todir="build" filtering="true">
      <fileset dir="base"/>
    </copy>
  </target>
 
  <target name="dist" depends="build" description="Creates the distribution code">
    <copy todir="dist">
      <fileset dir="build"/>
    </copy>
  </target>
</project>

The magic happens within <copy todir="build" filtering="true">. That tells Ant to copy the files to the ‘build’ directory and replace all the @cf.app.whatever@ tags with values found in the build.properties file mentioned earlier. After running this Ant script, we’ll have the following waiting for us in dist/config/mach-ii.xml:

<mach-ii version="1.6">
 
  <!-- . . . -->
 
  <properties>
    <property name="applicationRoot" value="/myproject" />
 
    <!-- . . . -->
 
  </properties>
 
  <!-- . . . -->
 
</mach-ii>

The goal is to have a perfect distribution of your application ready for you in the ‘dist’ directory. You could easily add additional Ant tasks to deploy your application to its home directory through FTP or a direct copy.

It doesn’t seem like much, but with these small changes I can now modify build.properties and deploy the application wherever I see fit, under any directory. Having multiple deployments on a single server, while not ideal, is perfectly doable.

Ant is an extraordinarily powerful build tool. I enjoy working with it and I’ve only barely tapped into its abilities. Hopefully if you’ve never evaluated it for one of your projects, you will soon. It has really made deployment a non-issue for the projects I use it on.

Post #5 done. Afk bourbon.

TransferObject Decorators and the Mach-II Event-Bean

Saturday, November 7th, 2009

If you’ve been following this blog, you know I’ve been trying to get TransferObjects to function as Mach-II event-beans. A problem that has been solved, I’m happy to say, with the help of Transfer Decorators.

The approach I took was to supplement my TransferObjects with methods that would only be used by the event-bean during form-to-bean population, and only for non-string methods. For example, my Course object has a ‘cost’ associated with it, which is a numeric. I added the following to my Course decorator:

  <cffunction name="getCostString" returntype="string" output="false">
    <cfreturn getCost()/>
  </cffunction>
 
  <cffunction name="setCostString" returntype="void" output="false">
    <cfargument name="costString" type="string" required="true"/>
    <cfif isNumeric(arguments.costString)>
      <cfset setCost(arguments.costString)/>
    </cfif>
  </cffunction>

Note that the functions and argument both reference a costString instead of just a cost. The costString will only be used by the event-bean population code, which means you need to modify your form:input tag as well:

      <label for="costString">Cost to Participants</label>
      $<form:input path="costString"/>

Once this code is in place, the event-bean works like a charm, populating all the fields you need without throwing those pesky errors due to string-to-numeric or string-to-date mistypes. It even works for checkboxes:

      <label for="educationFormatString">Education Format</label>
      <cfloop query="educationFormatQuery">
	<form:checkbox path="educationFormatString" value="#educationFormatQuery.id#"/>#educationFormatQuery.format#
      </cfloop>
  <cffunction name="getEducationFormatString" returntype="string" output="false">
    <cfset var returnString = ""/>
    <cfset var formats = getEducationFormatsArray()/>
    <cfloop from="1" to="#ArrayLen(formats)#" index="i">
      <cfif returnString neq ""><cfset returnString = returnString & ","/></cfif>
      <cfset returnString = returnString & formats[i].getId()/>
    </cfloop>
    <cfreturn returnString/>
  </cffunction>
 
  <cffunction name="setEducationFormatString" returntype="void" output="false">
    <cfargument name="educationFormatString" type="string" required="true"/>
    <cfset var array = ListToArray(arguments.educationFormatString)/>
    <cfset clearEducationFormats()/>
    <cfloop from="1" to="#ArrayLen(array)#" index="i">
      <cfset addEducationFormats(getTransfer().get("gorgon.EducationFormat", array[i]))/>
    </cfloop>
  </cffunction>

Since I’m using decorators anyway, why not just overwrite the methods for setting/getting numeric/date/checkboxes and be done with it? Well, I figured the solutions for populating event-beans and regular TransferObject usage might be completely different depending on the complexity of the bean; why go cluttering up a bean with extraneous method overloading and type-checking when I could just add a couple of methods that are only specifically used by one instance of the framework, and then ignored the rest of the time? Basically it’s a trade-off, and neither side is well explored territory so I erred on what I thought was the side of caution. I’m completely open to trying it the other way if the data suggest I made a design mistake.

So there you have it, after three iterations of attempts I’m finally binding forms to TransferObjects without writing too much code. Now we get to move out of the kiddy-pool and start coding for real. Hopefully this project will go forward more smoothly and will be much more maintainable now that I have what I consider a clean design from the start.

We shall see.

You Can’t Proxy TransferObjects in ColdSpring

Thursday, November 5th, 2009

In an attempt to use TransferObjects as event-beans I created a class called TransferObjectBindingAdvice that would wrap around the setter methods called by the Mach-II event-beans, to be installed and called via the AOP hooks in ColdSpring. I had high hopes that this proxied object would be able to take the place of the more advanced data binding hooks that I would’ve liked to see in the upcoming 1.9 release of Mach-II.

I wanted to be able to verify that setters expecting a Date object would get a valid date string, sure; but I also wanted to be able to bind more complex objects to the event-beans. For example, I could pass in the TransferFactory in the aspect’s declaration in the ColdSpring configuration file, and use it on a setter for a many-to-many relationship which would be represented as a checkbox group on a form. Simply loop through the list of IDs passed as that checkbox group and build your relationship, all before the controller gets called.

Unfortunately Sean Corfield pointed out this approach was technically impossible. He told me that TransferObjects are generated dynamically and the methods only exist at runtime, so they can’t be proxied by ColdSpring.

Which brings me back to square one. What is the best way to handle form-binding cleanly in Mach-II? Do I need to create model objects and be done with it? Should I use Transfer decorators? Should I give up hope of using TransferObjects, the event-bean command, and the <form> taglib in harmony?

Nah. Next up: decorators!

Using TransferObjects as Mach-II Event-Beans

Wednesday, November 4th, 2009

I wrote an article for the fantastic people at the Mach-II project called Using TransferObjects as Event-Beans. I was pretty jazzed at the opportunity to contribute back to the project that’s made software development so much fun for me lately.

A few days later, I’m starting to notice that the approach I was taking is an infeasible one, or at least a suboptimal one. The main goal I had in mind was code-reuse; since the TransferObjects are generated dynamically and Mach-II has the amazing event-bean command that populates a bean with form values automatically, that means I don’t have to write any code!

But of course, there’s a gotcha.

Mach-II doesn’t insist that the object being used as an event-bean only accept Strings as arguments, but it heavily hints at it. There are no hooks into the binding process (that I know of, please correct me if I’m wrong), so errors propagate up to the surface and the user gets a framework error instead of a nice “here’s what you did wrong” message. For example, if one of the fields is a Date, everything binds correctly if the user enters a convertible string, like “11/4/2009″. However, if the entry is blank (and this is true of numerical types too) a framework error is thrown.

And don’t even get me started on binding Select elements or Checkbox/Radio groups using TransferObjects.

Now, this isn’t the fault of the framework. In my zeal I’m trying to get it to do something that it wasn’t meant to do. But it raises some interesting questions on the ‘right way’ to handle form requests. It’s such a rudimentary operation that one would think it had been solved so satisfactorily that even children would know how to do it. I’m a pretty smart guy, why can’t I find a method I’m happy with?

Also, with Validation looming on the horizon (with the Mach-II 1.9 release), is my bonkers approach mainstream enough that I should be championing it to the development team? With decently open parameter binding sitting behind the event-bean command I could get my approach working very, very well, and the code would be absolutely pristine. But is it worth it? Do enough other people develop the ‘Mike way’?

The answer I think is more research. Look for open-source applications that are using Mach-II and see how they handle form submission. Perhaps start with the Dashboard (which is a wonderful piece of tech that makes my life a whole lot easier). There must be some OS projects out there using Mach-II for form submissions in a way that makes sense to me.

Or perhaps, should I abandon my desire to use TransferObjects as event-beans?