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.

Flex3 Apache Module glibc Gotcha

I went to install the Flex3 Apache compiler module on my server today, and it supports glibc 2.4 and higher only, which means no RedHat 4 (or derivatives).  So no Flex server compilation for me, since there doesn't seem to be source anywhere to compile against glibc 2.3.

Best Sign Ever

Excalibur – RPN Calulator for Windows

I've been using Galculator on Linux for a long while now, and I miss it when I'm at work on Windows.  It's not a particularly amazing piece of software, but it is simple, reliable, and easy to use.  So today I went looking for a Windows-compatible RPN calculator, and after trying a few different things that Google turned up, I found Excalibur.  It's a little less polished than Galculator, but it's far more feature rich.  Most important it's an RPN calculator that runs on Windows, and doesn't have any barriers to entry like some of the other alternatives had (like not picking up every keystroke or not having a stack view).

Speed vs. Cadence Chart

Just deployed a new Speed vs. Cadence chart on gpsRacr.com.  The chart is rather tangential to the ongoing development of the site (as rendering it cleanly requires significant manual babying of the data, both during recording and for selecting what to present), so it probably won't get updated again.  But it was a good exercise for getting familiar with the data and demonstrating that there's a solid base to work from.

For the most part, the assumptions I've made thus far are pretty sound it would seem.  The precision of the curves is ridiculously good.  The raw data has gone through a couple transformations before rendering, and the "still linear" nature (and the match up with data I observed externally while riding) indicates that my transformations aren't polluting the data to a significant degree.

I've got three more hours of "all by myself" this evening and what to do… what to do….

gpsRacr.com Has Content!

I finally got the import and summarization routines done for gpsRacr.com, and the initial data model is fully populatable (yes, I know that's not a word) from MotionBased transport packets.  That opens the door to finally start visualizing stuff, which is awesome.  First thing was plotting a speed vs. cadence chart, which is currently displayed on the homepage.  It's not much to look at, but it's a major step.

I've also nailed down a few external system interfaces, primarily Google Maps and Geonames.org for (surprise!) mapping and name lookups respectively.  There's also a lot of framework wiring that has been done, mostly to little benefit so far, but the payoffs of that will starting rolling in now that I'm ready to start working on the UI and business layers a bit more.

RequestMonitor.getRequestTimeout()

Ever wanted to know what the request timeout is for the currently-running ColdFusion request? The static 'getRequestTimeout()' method on the 'coldfusion.runtime.RequestMonitor' class knows. Tested to work on both CF7 and CF8.

Why might you want to know? So you can set a request timeout, but only if it's not lower than what is already set. Specific use case: I have a method that imports a data file and it takes a while, so I set a 120 second timeout. But I also have a method that imports a directory of data files, and want to set the timeout to quite a bit longer. Of course, the later method calls the former, and I don't want the former to be lowering the timeout I just set to allow for multiple files. So instead of this:

<cfsetting requesttimeout="120" />

I can do this instead:

<cfset monitor = createObject("java", "coldfusion.runtime.RequestMonitor") />
<cfsetting requesttimeout="#max(monitor.getRequestTimeout(), 120)#" />

and ensure that the timeout is a least 120 seconds, but let it stay higher if it already is.  Or, in handy UDF form:

<cfset requestMonitor = createObject("java", "coldfusion.runtime.RequestMonitor") />
<cffunction name="requireRequestTimeout" access="private" output="false" return="boolean"
  hint="I ensure the request timeout is at least what is specified, and return
    whether or not the timeout was increased by this call.">
  <cfargument name="timeout" type="numeric" required="true" />
  <cfif timeout GT requestMonitor.getRequestTimeout()>
    <cfsetting requesttimeout="#timeout#" />
    <cfreturn true />
  </cfif>
  <cfreturn false />
</cffunction>

JavaScript. At Last.

Working on gpsracr.com this evening, and whipped open my favorite chunk of JS: Prototype. It just gets better all the time. They've added support for automatically executing text that is returned from an Ajax.Request with an appropriate MIME type (like text/javascript), which means you don't even need result handlers. Just dispatch to your URL with whatever parameters you need, and let CF emit back whatever JS should run as a result of the submission (error, failure, success, whatever). Talk about sweet.

Now that might not sound like a big deal, but it really is. I use Fusebox. So all my links are parameterized with XFAs. But XFAs are server-side. Client-side JS (especially static JS in *.js files) can't really access XFAs very well, so URL building is a pain for remoting endpoints and/or hard context switches, But if you're generating the JS server-side….

The specific use case was a login form. I want to "submit" it asynchronously and either display a "denied" message if there's a failure, or forward to the inside of the app if it succeeds. Ordinarily, you'd have to submit the credentials, get some sort of status back from the server, and then use JS to do something with it. Now I can just submit the credentials and let CF emit the JS that I want to execute as a result. I love it.