E4X and Prototype Goodness

I got around to trying the E4X-based query structure this morning, and it works like a charm.  It's now possible to write your queries like this:

Query.execute(
  <query>
    update contact set
      name = :name,
      email = :email
    where id = :id
  </query>,
  {
    id: new QueryParam(id, "integer"),
    name: new QueryParam(name),
    email: new QueryParam(email)
  }
);

There's still an extra layer in there (i.e. the Query type and the <query> element), but the SQL is far more cleanly differentiated from the javascript, which is a big plus.  I've updated the core and sample application in Subversion with the changes to allow this syntax (the string-based syntax is still supported), along with various other little changes, including a full import of the browser-agnostic portions of Prototype (Object, Array, Hash, String, Template, etc.).

I also tried the "add the execute method to the XML class" route that I wrote about, and that doesn't work.  It seems that the E4X class (XML, XMLList, etc.) are sealed and can't have additional stuff added to them.  That's a bummer, but such is life.

More Thoughts on Server-Side JS

I got a lot of great comments (along with some not-so-great ones) regarding my server-side JS implementation. As expected, quite a few people were very excited about using AS3 on the server instead of JS, and I agree. It got me thinking about how much code reuse you could get within an application. At the very least, you can share libraries, which is handy. But you can also very likely share VO's, and quite possibly entity objects as well. If you're using AIR, and you're building your server-side in the appropriate ECMAScript, then you could theoretically port your server-side to the client side for use in standalone mode (i.e. persisting to SQLite), and then the "occasionally connected client" model becomes nothing more than two databases that you have to sync. No need to implement everything twice, because you're using the same technology in both tiers.

One big gripe (which I share) with script notation is the lack of tag bodies. CFQUERY and CFMAIL are wonderfully simple to use because their main textual parameter is just the tag's body. Having to synthesize that with string literals as you do in script is cumbersome to say the least. What I realized tonight, however, is that E4X (or ECMAScript for XML) isn't actually tied to ECMAScript 4, it's standalone, so it can be used with ECMAScript 3 implementations (such as Rhino). And that means that you can actually embed XML snippets directly in your code. I haven't tested it (had to work on other stuff this evening), but you should be able to take what I've got today for querying:

var get = new Query("select * from user where email is not null", "mydsn").execute();

and replace it with something like this:

var get = new Query(<query datasource="mydsn"><![CDATA[
select *
from user
where email is not null
]]></query>).execute();

While it's not quite as clean as the CFML version, it's a big step towards it. Better yet, Rhino supports E4X today, so this should work immediately. Not sure about WebKit (AIR's HTML/JS implementation), though a quick Googling indicates that as of this time last year there was nothing available, nor anything in the works. If only they'd used Gecko, which has supported it for a while. Now that I think about it, depending on the E4X implementation, it's entirely possible that you might be able to do this:

var get = <query datasource="mydsn"><![CDATA[
select *
from user
where email is not null
]]></query>.execute();

Doing that would depend on being able to extend the core E4X classes in the style of Prototype's extensions to Object and Array. Just throw an execute method on there that checks the root node of the doc it's called on as a selector for the desired action, and than hands off processing to the right subsystem, and you're done.

So much cool stuff…

Why JavaScript (or really, ECMAScript)?

After my last post on CF and JS integration (via Rhino), I got several comments that can be summarized as "why?". I addressed the issue in a rather oblique manner in my last post; here's a more full treatment.

Before I start, I want to make incredibly clear that I'm not talking about a new JS-based application server built on Rhino. I'm talking about using JS as an alternate/supplemental syntax for your CFML applications, which still run on the ColdFusion application server, with all the niceties that the CF server provides.

The first reason is that CFML is incredibly verbose for a lot of common tasks. Consider a function definition in CFML:

<cffunction name="doIt" output="false">
  <cfargument name="param1" />
  <cfargument name="param2" />
</cffunction>

Now the same definition in JS:

function doIt(param1, param2) {
}

The CFML is a lot more verbose, which is both more typing, and more characters to visually parse when you're trying to read the code. Of course, this isn't without downsides. Consider running a parameterized query in CFML:

<cfquery datasource="myDsn" name="get">
  select *
  from my_table
  where id = <cfqueryparam cfsqltype="cf_sql_integer" value="#id#" />
</cfquery>

And now in JS:

var q = new Query("myDsn", "select * from my_table where id = :id");
var get = q.execute({
  id: new QueryParam(id, "integer")
});

The CFML implementation definitely highlights the SQL as a standalone language embedded within the markup, while with the JS implementation, the SQL is just a string; there's nothing SQL-y about it. Is the more concise nature of the JS worth it in this specific case? I'd say it's close to a wash: less SQL delineation, but more concise code, more easily reusable SQL, semantically relevant (instead of positionally anchored) parameters, and reusable parameters. Note that in either case, I'm running my parameterized SQL against a datasource configured in the CF Administrator, and the 'get' variable will be a native CF query object (a 'coldfusion.sql.Table' instance).

Now where I really think JS (really ECMAScript, in general) has a lot to offer over CFML is in the language constructs itself. Nested functions, function literals (and their extension: closures) are a singularly fantastic language constructs. Rather than show a single example implemented in both CFML and JS, I'm going to just illustrate the concepts in JS, and contrast them with CFML. Nesting first:

function doSomething(param) {
  function f(o) {
    // do something ...
  }
  var result = f(param);
  for (var i in result) {
    if (typeof i == "object") {
      result[i] = f(result[i]);
    }
  }
}

So what's happening? I'm packaging up some reusable functionality into a function (named 'f'), but it's an implementation detail of the 'doSomething' function, and is therefore internal to the 'doSomething' function. Do do this same sort of "subpackaging" of functionality in CFML, you'd have to put the 'f' function outside as a peer of the 'doSomething' function (probably named 'doSomething_f' or whatever). That's a leak in encapsulation, as well as unnecessarily crowding the external namespace.

Function literals implicitly require function variables, the latter of which CF already has. A function variable just means you can pass around a function as a variable, and then call it later. A function literal, however, means you can define a function without declaring it. A simple example:

function declaredFunction() {
}
literalFunction = function() {
};

In the former case, there is an actual declaration of the function. It is it's own statement, and is compiled (or interpreted) as such. The latter, however, is a literal. It's an expression, and can be placed anywhere you might want to use an expression. Here's another example of doing something with declarations and then literals:

// declarations
function optionOne(){}
function optionTwo(){}
return (x == y) ? optionOne : optionTwo;

// literals
return (x == y) ? function(){} : function(){};

Assuming this is within another function, the declared functions in the first section could be nested, thereby minimizing their effect on namespace crowding. The second section, of course, doesn't have any extra declarations, though if the functions have any substantial size to them, it can get unreadable very quickly. As such, the latter syntax isn't always the right one, but it is nice to have the option. With CFML, you're again forced to explicitly declare the functions as peers.

And now closures. Closures are nested functions (declared or literal), which execute in the context of their definition location, even if they're executed after that context has gone away. Here's a (somewhat contrived) example:

function getFilter(name, value) {
  return function(item) {
    return item[name] == value;
  }
}
function conditionallyDoSomething(collection, filter) {
  for (var i = 0; i < collection.length; i++) {
    if (filter(collection[i]) {
      // do something ...
    }
  }
}
conditionallyDoSomething(people, getFilter("age", 27));

This is a fairly contrived example, as I said, but it illustrates the concept. By the time the function returned from 'getFilter' is executed, the 'getFilter' function has terminated. However, it (the function literal) still has access to the context it was defined in – namely the 'getFilter' function's context, and therefore it's parameters. Closures are really the crown jewel of ECMAScript, in my opinion. With CFML, the closure is totally undoable, unless you use some sort of synthesis framework like Sean's.

The final advantage, like the querying example above, cuts both ways. JS-based implementations can't use any existing CFML tools (Fusebox, ColdSpring, etc.), but they can use all the myriad JS tools that are available. I happen to be a big fan of Prototype, and I can use the UI-agnostic pieces (like the Object and Array extensions) of Prototype on the server-side. Similarly, if you write some wicked-cool routine for your server-side processing, you can immediately and directly reuse it in your client-side JS (or quite possibly AS).

So between the enormously lighter language syntax and the additional language functionality, I think there's a compelling case for using JavaScript (or ECMAScript) as a language for building apps on the ColdFusion server. In an ideal world, it's be ECMAScript 4th Edition (what ActionScript 3 is based on), but there isn't a mature, Java-based interpreter for it anywhere. Aside from just generally being more modern, the 4th Edition provides strong typing (using colon-postfix notation), real packages and classes, interfaces, object member access levels, and a few other neat things.

One very interesting aspect of all this is that Adobe happens to own an ECMAScript 4 compiler (the AS3 compiler from the Flex SDK), an ECMAScript 4 runtime (the Tamarin project that underlies Flash Player 9 and is now hosted by the Mozilla Foundation), and ColdFusion.  So it seems like using ECMAScript 4 as an alternative to CFML for the CF Server is really not that outlandish.  The pieces are all done, it's just the integration that is lacking, and CF is all about integration.  Rather to rich of a project for me to undertake on my own (hence ECMAScript 2 and Rhino), and the closed nature of CF makes it rather hard as well, but for an entity like Adobe, quite a reasonable undertaking, I'd think.  Here's hoping for CF9!

The Rhino Is Coming … to fix CFML

Show of hands, how many people like CFML? Ok, how many like the ColdFusion Platform? What's the difference, you ask? CFML is a markup language that you do most CF Platform development in. You can use CFSCRIPT (which is ECMAScript-like) for certain things, but CFML is the Platform workhorse. The Platform, on the other hand, is all the stuff that your applications (written in CFML, usually) get because they run on the CF server. Things like datasources, PDF generation, easy file access, etc. That's the platform, not the CFML language.

What if I said you could build applications on the CF Platform but weren't required to use the often-cumbersome CFML? What if it were a language that you already new? What it it was JavaScript? And what does that have to do with rhinos?

Rhino is a free JavaScript implementation from Mozilla. It's what drives Firefox, and is also the base implementation of the new Java scripting framework that is available in Java 6 (the javax.script package). It's entirely implemented in Java, and since ColdFusion is as well, that means that CFML within CF and JS within Rhino can execute in the same context, intermingled with each other.

Here's a simple proof-of-concept contact manager that I build over the weekend (running on my underpowered server). You can download the source as a ZIP archive, or via anonymous SVN (with SVN always being current, of course).  Of perhaps more general interest is the class explorer mini-app, which is included in the distribution. As you might imagine, getting everything wired up took some digging around in the Java internals, and this provides a window to the classes that are actually executing inside the JVM. Helpful for both exploration and troubleshooting classpath issues.

The app is in two parts: the integration core within the 'rhino' subdirectory, and the sample app itself. I'll discuss them separately (and very briefly) below.

The basic architecture of the app is to use JS for the business logic and backend processing, and use CFML only for the view layer (where it excels). 'Application.cfm' and 'index.cfm' can be considered "framework" files; there is no application logic in there, just wiring stuff. The views are all in the 'views' subdirectory, and the backend is in the 'scripts' subdirectory.

The architecture of the Rhino integration is centered around 'rhino.cfc' and it's 'runScript' method. You pass that method a JS file to execute, and before evaluation, it will marshal some CF objects into the JS runtime. Following execution, it will unmarshal the top-level 'model' object back to CF for use in rendering your views. More simply, you pass in your parameters and your JS script, it executes, and you get a 'model' structure back to do something with. There is a special global function named 'loadScript' that can be used at runtime to pull in additional scripts, rather like CFINCLUDE.

As you'd expect, JS files are "precompiled" into in-memory script objects and cached until they update on disk, just like CF does with CFM templates. Unlike CF, however, this doesn't seem to offer a particularly significant performance improvement. I don't have a trusted-cache option at the moment, so there is still the filesystem check per-file, per-request, which is probably part of the reason. This actually happens in two places, once to bootstrap the environment (in 'rhino.cfc') and one for the actual runtime (in 'core/bootstrap.js'). The dichotomy annoys me, but such is life.

So why did I spend 15 or 20 hours putting this together? Because I'd love to be able to write my server-side with ECMAScript (though I'd prefer ActionScript 3 to JavaScript 1.6), and wanted to do a little proof of concept for that. I quite like the CF platform, but I'm not a huge fan of CFML itself. How supremely excellent would it be for CF9 to natively support scripting in ActionScript as well as CFML/CFScript, with equal access to the same underlying platform?

CF8 Structure Literal Gotcha

This one has gotten me several times.  With the new structure literal notation in CF8, you have to use equal signs between the key-value pairs.  In other places where you use key-value pairs (like passing named params to a UDF), you can use equals signs or colons, but not so with structure literals.
1xSlots Casino https://1xslot.ru/
I have to say, I was quite excited for structure and array literals in CF8, but man have I been disappointed.  No nesting cripples them to near worthlessness, and this (seemingly random) inconsistency adds further annoyance.  I'd have been happier if they'd just been omitted until they could have been done reasonably.

Noise Canceling Headphones

At our office, we've recently moved to a shared workspace instead of individual offices, and part of the deal was that we each got a set of noise canceling headphones (PXC 300 from Sennheiser).  I was skeptical, but figured pumping music directly into my ears coupled with the muffling effect of the actual headphones would be sufficiently "noise canceling".  Oh, was I in for a surprise.

We finally got them today and I am impressed.  Like not "yeah, they do make it quieter" impressed, but "holy <deleted>, it's like a <deleted>ing concert hall" impressed.

Cyclic Graph Safe CFDUMP Replacement? Check.

On the Fusebox 5 mailing list this evening, Brian Kotek mentioned the inability to CFDUMP the myFusebox object because CFDUMP can't handle the cyclic nature of the object's internal data structures. While dumping myFusebox (which is a CFC instance) is probably a "silly" thing to do (Sean Corfield from the same thread: "Mind you, since myFusebox is an *object*, I'm not sure why you're dumping it?"), the general problem of CFDUMP overflowing the stack while dumping a recursive data structure is a valid one.

I've been bitten by it a few times in the past, and always worked around it by manually breaking the cyclic references before dumping. That, of course, is error prone and requires an intimate knowledge of the object being dumped, which is often not the case.

Since the kids are in bed and Heather's out this evening, I took it upon myself to create a CFDUMP replacement (or at least the beginnings of one) that handles recursion gracefully. It took about 45 minutes to write, and if anyone on the CF team is out there, please feel free to copy my code into the CFDUMP tag (or send me the source, and I'll patch it myself for no compensation other than the right to blog that I did it).

Here it is, in all it's glory (save it to 'dump.cfm' somewhere). I've comment-wrapped the 13 lines of code (lines 6-11 and 84-89) that deal with cyclic graph protection; the rest is just stock dump stuff.

<cfimport prefix="u" taglib="." />

<cfif thistag.executionMode EQ "start">
  <cfparam name="attributes.var" />
  <!--- cycle protection --->
  <cfif NOT structKeyExists(attributes, "__visited__")>
    <cfset attributes.__visited__ = createObject("java", "java.util.HashMap").init() />
  </cfif>
  <cfif NOT structKeyExists(attributes, "__path__")>
    <cfset attributes.__path__ = "root" />
  </cfif>
  <!--- /cycle protection --->

  <cfset initKey = "__U_DUMP_INITIALIZED__" />

  <cfif NOT structKeyExists(request, initKey)>
    <cfoutput>
    <style type="text/css">
      /* global */
      table.udump th {
        padding: 3px 5px;
      }
      table.udump td {
        background-color: ##fff;
        vertical-align: top;
        padding: 2px 3px;
      }
      /* query */
      table.udump-query {
        background-color: ##848;
      }
      table.udump-query tr.label th.query {
        background-color: ##a6a;
        color: ##fff;
        font-weight: bold;
        text-align: left;
      }
      table.udump-query tr.fields th,
      table.udump-query td.row-number {
        background-color: ##fdf;
        font-weight: normal;
      }
      /* struct */
      table.udump-struct {
        background-color: ##00c;
      }
      table.udump-struct tr.label th.struct {
        background-color: ##44c;
        color: ##fff;
        font-weight: bold;
        text-align: left;
      }
      table.udump-struct td.key {
        background-color: ##cdf;
      }
      /* array */
      table.udump-array {
        background-color: ##060;
      }
      table.udump-array tr.label th.array {
        background-color: ##090;
        color: ##fff;
        font-weight: bold;
        text-align: left;
      }
      table.udump-array td.index {
        background-color: ##cfc;
      }
    </style>
    </cfoutput>
    <cfset request[initKey] = true />
  </cfif>
<cfelseif thistag.executionMode EQ "end">
  <cfset System = createObject("java", "java.lang.System") />

  <cfif isSimpleValue(attributes.var)>
    <cfif len(trim(attributes.var)) EQ 0>
      <cfoutput>[empty string]</cfoutput>
    <cfelse>
      <cfoutput>#attributes.var#</cfoutput>
    </cfif>
  <cfelse>
    <!--- cycle protection --->
    <cfset hashCode = System.identityHashCode(attributes.var) />
    <cfif attributes.__visited__.containsKey(hashCode)>
      <cfoutput>[already dumped : #attributes.__visited__.get(hashCode)#]</cfoutput>
      <cfexit method="exittag" />
    </cfif>
    <cfset attributes.__visited__.put(hashCode, attributes.__path__) />
    <!--- /cycle protection --->

    <cfif isQuery(attributes.var)>
      <cfset fields = attributes.var.columnList />
      <cfoutput>
      <table class="udump udump-query">
      <tr class="label">
        <th class="query" colspan="#listLen(fields) + 1#">query - #attributes.var.recordCount# records</th>
      </tr>
      <tr class="fields">
        <th> </th>
        <cfloop list="#fields#" index="field">
          <th>#field#</th>
        </cfloop>
      </tr>
      <cfloop query="attributes.var">
        <tr>
          <td class="row-number">#currentRow#</td>
          <cfloop list="#fields#" index="field">
            <td><u:dump var="#attributes.var[field][currentRow]#"
              __visited__="#attributes.__visited__#"
              __path__="#attributes.__path__#.#field#[#currentRow#]" /></td>
          </cfloop>
        </tr>
      </cfloop>
      </table>
      </cfoutput>
    <cfelseif isStruct(attributes.var)>
      <cfoutput>
      <table class="udump udump-struct">
      <tr class="label">
        <th class="struct" colspan="2">struct - #structCount(attributes.var)# keys</th>
      </tr>
      <cfloop list="#listSort(structKeyList(attributes.var), 'textNoCase')#" index="i">
        <tr>
          <td class="key">#i#</td>
          <td><u:dump var="#attributes.var[i]#"
            __visited__="#attributes.__visited__#"
            __path__="#attributes.__path__#.#i#" /></td>
        </tr>
      </cfloop>
      </table>
      </cfoutput>
    <cfelseif isArray(attributes.var)>
      <cfoutput>
      <table class="udump udump-array">
      <tr class="label">
        <th class="array" colspan="2">array - #arrayLen(attributes.var)# items</th>
      </tr>
      <cfloop from="1" to="#arrayLen(attributes.var)#" index="i">
        <tr>
          <td class="index">#i#</td>
          <td><u:dump var="#attributes.var[i]#"
            __visited__="#attributes.__visited__#"
            __path__="#attributes.__path__#[#i#]" /></td>
        </tr>
      </cfloop>
      </table>
      </cfoutput>
    <cfelse>
      <cfoutput>[unknown object]</cfoutput>
    </cfif>
  </cfif>
</cfif>

And here is a simple test case (save it to a new file in the same directory as the 'dump.cfm' you just created).

<cfimport prefix="u" taglib="." />

<cfset b = structNew() />
<cfset b.name = "barney" />
<cfset b.age = 27 />

<cfset h = structNew() />
<cfset h.name = "heather" />
<cfset h.age = 27 />

<cfset b.spouse = h />
<cfset h.spouse = b />
<cfset b.anniversary = createDate(2002, 8, 3) />
<cfset h.anniversary = b.anniversary />

<cfset l = structNew() />
<cfset l.name = "lindsay" />
<cfset l.age = 3 />
<cfset l.mom = h />
<cfset l.dad = b />

<cfset e = structNew() />
<cfset e.name = "emery" />
<cfset e.age = 2 />
<cfset e.mom = h />
<cfset e.dad = b />

<cfset c = arrayNew(1) />
<cfset arrayAppend(c, l) />
<cfset arrayAppend(c, e) />

<cfset b.children = c />
<cfset h.children = c />

<cfset q = queryNew("id,name", "integer,varchar") />
<cfset queryAddRow(q) />
<cfset querySetCell(q, "id", 42) />
<cfset querySetCell(q, "name", "brian") />

<cfquery dbtype="query" name="get">
  select *
  from q
</cfquery>

<cfset b.recordSet = get />

<u:dump var="#b#" />

The code (both dump.cfm and the test case) are public domain, so you can use them however you'd like. I'd prefer credit be provided (at least a name and URL) where appropriate, but it's up to you.

Dooce Got …Dooced? …Again?

Getting Slashdotted is a thing of the past, now you can get Dooced: post an entry that causes your feed subscribers to actually visit the site itself, and overwhelm your server.  Note this is definition two for 'dooced', the first being "to have posted work-related things on your personal blog and get fired for it."

Reflection on CF8's Image Tooling

I was really hoping for (though not really expecting) a nice simple solution to centering text on an image to show up as a result of the Wednesday Contest. Sadly, that wasn't the case. As you might guess, the contest was based on a real world use case that I had: gracefully handling the image tooling's inability to generate thumbnails of certain images.  The usual culprit seems to be color profile issues (which cause fatal errors), but I also get a number of thumbnails that come out with horribly screwed up colors (not actual negatives, but close). I should mention that I'm not doing anything special to create the thumbnails, just resizing the image to within certain bounds (usually 150×150 or 100×100).

I hadn't had the resizing problem with my JAI-based solution (which piggy-backed on the Lite/DevNet edition of Alagad Image Component), so I'd never had occasion to drop in error handling until switching to CF8's built-in toolkit. I didn't want to spend much time on it, just wanted it to not throw errors and create unbroken images, so I picked a pretty simple (I thought) task. This was the task I posed as the Wednesday Contest, though my implementation actually uses three lines instead of one (for "Error Generating Thumbnail"). I'd originally wanted to use 12pt bold instead of the default (10pt plain), but after seeing the implementation cost of using a non-default font (default font implementation vs. custom font implementation), I bailed on that and used the default.

I'd be lying if I said I wasn't disappointed. Centering text seems like a fairly basic task, but it's far from simple to do. I went back to the docs several times sure that I'd missed something, but unless I'm an idiot, there's nothing there.

Wednesday Contest Solution (pt. 2)

And here's the extra credit solution:

<cfif NOT isDefined("attributes")>
  <cfset attributes = structNew() />
  <cfset structAppend(attributes, form, false) />
  <cfset structAppend(attributes, url, false) />
</cfif>
<cfparam name="attributes.width" default="100" />
<cfparam name="attributes.height" default="100" />
<cfparam name="attributes.backgroundColor" default="f7f7f7" />
<cfparam name="attributes.borderColor" default="cccccc" />
<cfparam name="attributes.textColor" default="990000" />
<cfparam name="attributes.text" default="Hello!" />
<cfparam name="attributes.textSize" default="16" />
<cfparam name="attributes.textStyle" default="bolditalic" />
<cfparam name="attributes.textFont" default="courier new" />

<cfset fontArgs = structNew() />
<cfif structKeyExists(attributes, "textSize") AND isNumeric(attributes.textSize)>
  <cfset fontArgs["size"] = attributes.textSize />
</cfif>
<cfif structKeyExists(attributes, "textStyle") AND len(trim(attributes.textStyle)) GT 0>
  <cfset fontArgs["style"] = attributes.textStyle />
</cfif>
<cfif structKeyExists(attributes, "textFont") AND len(trim(attributes.textFont)) GT 0>
  <cfset fontArgs["font"] = attributes.textFont />
</cfif>

<cfset image = imageNew("", attributes.width - 2, attributes.height - 2, "rgb", attributes.backgroundColor) />
<cfset imageAddBorder(image, 1, attributes.borderColor) />

<cfset imageSetAntialiasing(image, "on") />
<cfset imageSetDrawingColor(image, attributes.textColor) />

<cfset graphics = imageGetBufferedImage(image).getGraphics() />
<cfset originalFont = graphics.getFont() />

<!--- default the fontArgs struct based on the default font --->
<cfif NOT structKeyExists(fontArgs, "font")>
  <cfset fontArgs.font = originalFont.getFontName() />
</cfif>
<cfif NOT structKeyExists(fontArgs, "size")>
  <cfset fontArgs.size = "" & originalFont.getSize() />
</cfif>
<cfif NOT structKeyExists(fontArgs, "style")>
  <cfif originalFont.isBold() AND originalFont.isItalic()>
    <cfset fontArgs.style = "bolditalic" />
  <cfelseif originalFont.isBold()>
    <cfset fontArgs.style = "bold" />
  <cfelseif originalFont.isItalic()>
    <cfset fontArgs.style = "italic" />
  <cfelse>
    <cfset fontArgs.style = "plain" />
  </cfif>
</cfif>

<!--- create the new font --->
<cfset font = createObject("java", "java.awt.Font") />
<cfif fontArgs.style EQ "bolditalic">
  <cfset style = bitOr(font.BOLD, font.ITALIC) />
<cfelseif fontArgs.style EQ "bold">
  <cfset style = font.BOLD />
<cfelseif fontArgs.style EQ "italic">
  <cfset style = font.ITALIC />
<cfelse>
  <cfset style = font.PLAIN />
</cfif>
<cfset newFont = font.init(fontArgs.font, javaCast("int", style), javaCast("int", fontArgs.size)) />

<!--- get the FontMetrics, this time for the new font --->
<cfset bounds = graphics.getFontMetrics(newFont).getStringBounds(attributes.text, graphics) />

<!--- draw it --->
<cfset imageDrawText(
  image,
  attributes.text,
  (attributes.width - bounds.getWidth()) / 2,
  (attributes.height + bounds.getHeight()) / 2,
  fontArgs
) />

<cfimage action="writeToBrowser"
  source="#image#" />

The implementation is fairly generic, though it contains no error handling, so it's still possible for fatal errors to crop up (particularly with font names). Quite a lot of work for some simple centered text.