Monthly Archive for September, 2007

CF8 and Batik (for SVG)

I just discovered that CF8 ships with a more complete Batik than CF7 did. Out of the box it's capable of transcoding SVG graphics to PNGs, which CF7's implementation wasn't capable of doing. With CF7 you had to move the partial Batik that came bundled and replace it with a full version to get transcoding to work, but no more. I'm not sure what CF8 uses it for, but CF7 appeared to use it for CFFORM stuff. Perhaps that's been extended and requires a full Batik, or maybe something else does. My initial thought was maybe the new Image libraries supported SVG, but that doesn't seem to be the case.

"Why SVG?", you ask? Because CFCHART is remarkably inflexible (and rather clumsy to boot) to the point that I wrote my own charting engine. It's hardly a CFCHART replacement, but far better suited to some of the charts that I need to produce. I chose SVG (several years ago, mind you) because it was widely supported and it's all text based, which makes generating it with CF a breeze. It also works as-is, or can be transcoded to a "normal" graphic (though you lose the vector base if you go to a raster format) for easier consumption.

Edit: Rob asked about the charts in the comments, so here's an example chart: capacity.png.  It doesn't really represent the full capabilities (area plots, multiple y-axes, etc.), but you get the idea.  The x-axis is labeled with age in days, with zero being midnight this morning.  If you've ever seen/used RRDTool (often paired with MRTG) the idea is similar, except that my database isn't round robin, it's historical, so I can change the time period on my dashboards and get charts back as far as I have data, if I want them.  Here's the same chart with data back to the beginning: capacity2.png.  As you can see, tracking of the 'imageCapacity' series started later (by about six months) than tracking 'totalImageCount'.  And no, those execution times (in the lower right) aren't representative - I grabbed these after a fresh restart so caches were empty.

CF8 Mail Spool Directory

After a couple days of CF8, I noticed I wasn't getting the emails I was supposed to be getting. Turns out the CF8 installer, at least in multi-server mode, doesn't set the right permissions on the /WEB-INF/cfusion/Mail subdirectories, so CF can't write out the mail spool files. Flipping them to the right group and allowing group write access solved the issue. I seem to recall having to do that with certain installations of CF7 and CF6.1 as well, but I couldn't tell you what the common the thread was (or if it existed at all).

CF8 Scheduled Task Migration

Turns out the migration of my scheduled tasks wasn't quite perfect.  A couple of them hit a URL protected by HTTP Basic Authentication, and the password didn't translate correctly.  It appears they are stored encoded, so I expect the encoding scheme changed.  Worse, the encoded value of one of them had a double quote which wasn't escaped, so it broke the edit form.  That's why htmlEditFormat() was created.  Oops…

My ColdFusion 8 Upgrade

I just upgraded my workstation to ColdFusion 8 today, as well as installed it on my server (though it's just testing at this point).  Since it's my first real-world CF8 experience, I figured everyone would want to hear all about it, especially the new features that made the process much easier.

The real motivation for the upgrade was the refusal of the 7.0.1 and 7.0.2 installers to upgrade my 7.0.0 CF instance, saying it wasn't a valid JRun.  I suspect it's because it's actually a CF6.1 JRun with a CF7 instance inside, but whatever.  I opted for installing CF8 into it's own separate JRun to avoid that potential issue down the road.

First off was the automatic upgrade migration.  I have no idea how it works, because I didn't tell CF8 where my CF7 lived (and it wasn't in a "standard" location), but it worked.  Kudos for that.  Next up, poking around the administrator, I noticed the setting to disable runtime CFC type checks, which is a nice way to get the benefits of type checking during development without the performance overhead in production.  I'm surprised I hadn't heard of this feature, as it definitely adds a new dimension to the perpetual "duck typing in CF" debate.

The next bit was converting my old MySQL datasources to the new built-in Connector/J.  It would have been nice if there were a button to convert an old (3.x)  datasource to a new (4.x/5.x) one automatically, but I only had 10 or 12, and they were manually dispatched in five or six minutes.

At this point, I had live code, and I wanted to play with the new server monitoring dashboard, but being a Flex app, it repeatedly crashed Firefox (on Linux), so I gave up.  The demos gave the impression that it's pretty slick, but I guess I won't know until we get it loaded at work (where we run Windows).  But it's a "candy" feature, so a glitch (even a fatal one) isn't a big deal.

Digging into the code a bit and I realized the ByteArray issue with the new MySQL drivers is pretty commonplace, so I had to do some recoding (adding explicit casts to CHAR) of my apps to get them to work right.  No big deal, and definitely worth it for the newer drivers.  If you're not familiar with the problem, certain functions (GROUP_CONCAT in particular) end up returning a ByteArray instead of a string, which CF balks at using.  Casting to CHAR in the DB, or using as the source of a new java.lang.String in CF both resolve the problem - take your pick.

Next was refactoring to use the new image manipulation libraries instead of my custom stuff.  I'd used the Alagad component from one of the DevNet CDs as my base, and then subclassed it to add various other manipulations I needed.  Unfortunately, something's wonky with the JAI upgrade, because all the ops I tested using Alagad and my extensions ended up with color-thrashed images, so that kind of forced my hand before I was ready.  Since I still have to run on CF7 (in production) I couldn't just do a wholesale replace, but CF8 and ColdSpring saved the day.

Using the import functionality of ColdSpring, I split my beans XML into version-specific beans and general beans, and then use a version switch to pick the right XML to load into the factory.  All well and good, but typechecks still happen, and I didn't want to have my CF8-based manipulation object have to extend my Alagad subclass.  CFINTERFACE to the rescue!  Moved my subclass from Image to ImageImpl_CF7, created a new Image interface for it to implement, and then added a second implementation that is based on the CF8 built-ins.  No type changes anywhere in the application.  Sooooooooo nice.

I was happy to see that CF8 exposes a imageGetBufferedImage() for returning the java.awt.BufferedImage backing a CF Image object ,so you can still do your own raw manipulations as part of a pipeline based on CF8's built-ins.  Fortunately, CF8 covered all the extensions I'd added to Alagad, so all of my custom stuff was unneeded.  The built-ins also seem to be faster than what I was using before, which is nice.  I assume there's a bunch of native code in there (thanks Photoshop!) rather than an all-Java solution.  Didn't do any format testing, but generating piles and piles of thumbnails seemed to progress faster than I recall with CF7.  I'm not sure the quality is as good (it didn't look like it), but in the absence of empirical tests (or even a comparison of the JPG compression settings), I'll withhold judgment.

I also noticed is a couple minor changes to CSS processing of CFDOCUMENT.  Nothing really broken or fixed compared to CF7, but wrong in different ways (not respecting print stylesheets correctly - while both FF and IE do it right).  Nothing I'm going to try and go work around, but it'd piss off branding Nazis, I'd wager.  ; )

I didn't get a chance to play with per-application mappings, because unfortunately they're only available from Application.cfc, which has some problems (like onRequest shadowing remote CFC calls).   This is probably the biggest feature I'd want, because I hate having to keep all my apps running on identical ColdSpring, but rearchitecting to get deal with Application.cfc is a big burden (heck, it's a burden even for new apps).

Things got a bit worse with the test install on my production server.  First, the wonderful browser requirements for Adobe.com (which I've complained about before) necessitate downloading locally, and then immediately pushing it right back out.  My bandwidth is free, but it still takes a while to feed that much data across the wire twice.

Next was trying to get JRun to start on system init.  CF7 (I have Standard Edition) has a reasonably nice installer for a init script.  Nothing with a multi-server install of CF8.  I'm assuming it's the "multi-server" that is the problem, but it's still just firing off JRun either way, so I'm not sure what the distinction is.

Connecting to Apache was a dream - the best connector experience I've had with CF6+.  I've never been stuck up a creek, but there's usually been some little glitch somewhere.  Both machines - no problems.

And as a final extra benefit, CF8, courtesy of it's built-in LiveCycle Data Services has server-side Flex compilation, so the refusal of the web connector from Labs to work on my machines is no longer an issue.

All in all, a solid release.  Now I've got 30 days to determine whether or not it's worth paying for the upgrade for the server.  None of the feature improvements really justify the cost (monetary and otherwise), but keeping current has a tangible value, so the $649 is probably right on the cusp of "reasonable".

Flex2 (and 3) RemoteObjects over SSL with ColdFusion

I just deployed a Flex app to an SSL secured host, and ran into some issues getting AMF over SSL working.  Googling turned up the answers, but in rather fragmented form.  So I'm coalescing them here.  In a nutshell, you have to create a new channel (my-secure-amf) that uses the secured versions of the AMF classes (both AS and Java), and then register it as a usable channel for the ColdFusion destination.  Here's a diff from my services-config.xml (in /WEB-INF/flex) with the four important bits bolded:

--- services-config.xml 2007-09-01 00:37:25.000000000 -0700
+++ services-config.xml 2007-09-01 00:59:41.000000000 -0700
@@ -13,6 +13,7 @@
             <destination id="ColdFusion">
                 <channels>
                     <channel ref="my-cfamf"/>
+                    <channel ref="my-secure-cfamf"/>
                 </channels>
                 <properties>
                     <source>*</source>
@@ -48,6 +49,15 @@
                 </serialization>
             </properties>
         </channel-definition>
+        <channel-definition id="my-secure-cfamf" class="mx.messaging.channels.SecureAMFChannel">
+            <endpoint uri="https://{server.name}:{server.port}{context.root}/flex2gateway/" class="flex.messaging.endpoints.SecureAMFEndpoint"/>
+            <properties>
+                <polling-enabled>false</polling-enabled>
+                <serialization>
+                    <instantiate-types>false</instantiate-types>
+                </serialization>
+            </properties>
+        </channel-definition>
     </channels>

     <logging>

In addition to this server-side config, you need to ensure your client connects to the proper channel and with the proper Channel (the AS class) implementation.  If you're using services-config.xml in your compilation process, it's a no brainer, but if you're manually configuring your ChannelSet in code (parameterized, of course), you have to watch out.  If you want to do the latter, here's how I do it:

private function configRemoteObject(gateway:String, cfc:String, useSsl:Boolean):RemoteObject {
  var cs:ChannelSet = new ChannelSet();
  if (useSsl) {
    cs.addChannel(new SecureAMFChannel("my-secure-cfamf", gateway));
  } else {
    cs.addChannel(new AMFChannel("my-cfamf", gateway));
  }
  var ro:RemoteObject = new RemoteObject();
  ro.destination = "ColdFusion";
  ro.channelSet = cs;
  ro.source = cfc;
  ro.addEventListener(ResultEvent.RESULT, function(event:ResultEvent):void {
    if (event.token.hasOwnProperty("resultHandler")) {
      event.token.resultHandler(event);
    } else {
      trace("result event with no resultHandler: " + event);
    }
  });
  ro.addEventListener(FaultEvent.FAULT, function(event:FaultEvent):void {
    if (event.token.hasOwnProperty("faultHandler")) {
      event.token.faultHandler(event);
    } else {
      trace("FaultEvent: " + event.fault.message);
    }
  });
  return ro;
}

The three arguments to the method are pulled in via a URLLoader to an XML file such as the one below that is part of the deployment configuration (i.e. not part of the codebase).

<?xml version="1.0" encoding="utf-8"?>
<settings>
  <gateway>/flex2gateway/</gateway>
  <useSsl>false</useSsl>
  <cfc>sorter.cfcs.count_facade</cfc>
</settings>

I don't claim that this is the best way, but it does let you easily change where your SWF connects to without recompilation, which therefore lets you build your Flex projects standalone (no compile-time server linkage).  Quite useful if you have a variety of environments you want a single project/SWF to work in.