<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>BarneyBlog &#187; cfml</title>
	<atom:link href="http://www.barneyb.com/barneyblog/category/cfml/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.barneyb.com/barneyblog</link>
	<description>Thoughts, rants, and even some code from the mind of Barney Boisvert.</description>
	<lastBuildDate>Mon, 02 Mar 2020 13:20:35 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title>Boggle Boards</title>
		<link>https://www.barneyb.com/barneyblog/2011/07/20/boggle-board/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/07/20/boggle-board/#comments</comments>
		<pubDate>Thu, 21 Jul 2011 05:13:24 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[cfml]]></category>
		<category><![CDATA[groovy]]></category>
		<category><![CDATA[random]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1710</guid>
		<description><![CDATA[In case anyone wants to know, here are the specs for Boggle &#8211; both for Original Boggle and for Big Boggle &#8211; in a handy machine-readable format.  The format is line oriented with each line representing a single die, and the sides of the dice delimited by spaces.  Note that there is a [...]]]></description>
			<content:encoded><![CDATA[<p>In case anyone wants to know, here are the specs for Boggle &#8211; both for Original Boggle and for Big Boggle &#8211; in a handy machine-readable format.  The format is line oriented with each line representing a single die, and the sides of the dice delimited by spaces.  Note that there is a side with 'Qu' on it, so you must allow for a multi-letter side in your parser.</p>
<table>
<tbody>
<tr>
<td><a href="http://www.barneyb.com/boggle/original_boggle.txt">original_boggle.txt</a></p>
<pre>A A C I O T
A B I L T Y
A B J M O Qu
A C D E M P
A C E L R S
A D E N V Z
A H M O R S
B F I O R X
D E N O S W
D K N O T U
E E F H I Y
E G I N T V
E G K L U Y
E H I N P S
E L P S T U
G I L R U W</pre>
</td>
<td><a href="http://www.barneyb.com/boggle/big_boggle.txt">big_boggle.txt</a></p>
<pre>A A A F R S
A A E E E E
A A F I R S
A D E N N N
A E E E E M
A E E G M U
A E G M N N
A F I R S Y
B J K Qu X Z
C C E N S T
C E I I L T
C E I L P T
C E I P S T
D D H N O T
D H H L O R
D H L N O R
D H L N O R
E I I I T T
E M O T T T
E N S S S U
F I P R S Y
G O R R V W
I P R R R Y
N O O T U W
O O O T T U</pre>
</td>
</tr>
</tbody>
</table>
<h2>Usage</h2>
<p>Here is a simple CFML function which accepts a board definition and returns an array representing a "roll" of the grid:</p>
<pre>&lt;cffunction name="roll" output="false" returntype="array"&gt;

   &lt;cfargument name="board" type="string" required="true" /&gt;
   &lt;cfset var result = [] /&gt;
   &lt;cfloop list="#board#" index="die" delimiters="#chr(10)#"&gt;

      &lt;cfset die = listToArray(die, ' ') /&gt;
      &lt;cfset arrayAppend(result, die[randRange(1, arrayLen(die))]) /&gt;
   &lt;/cfloop&gt;
   &lt;cfset createObject('java', 'java.util.Collections').shuffle(result) /&gt;
   &lt;cfreturn result /&gt;
&lt;/cffunction&gt;
</pre>
<p>And the result of executing it on the original board:</p>
<pre>[N, T, W, A, B, Qu, B, D, L, M, D, H, D, L, G, V]
</pre>
<p>Here's a Groovy Closure which does the same thing:</p>
<pre>{
   it = it.tokenize('\n')
      .collect{ it.tokenize(' ') }
      .collect{ it[new Random().nextInt(it.size)] }
   Collections.shuffle(it) // icky!
   it
}
</pre>
<p>And the result of executing it on the big board:</p>
<pre>[N, E, G, N, N, C, S, A, F, E, U, N, H, I, P, H, C, T, T, O, I, O, Qu, T, T]
</pre>
<p>NB: The source for this post can be found at <a href="http://www.barneyb.com/boggle/">http://www.barneyb.com/boggle/</a>.Â  Any updates I may have will go there.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/07/20/boggle-board/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Visiting Recursion</title>
		<link>https://www.barneyb.com/barneyblog/2011/06/16/visiting-recursion/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/06/16/visiting-recursion/#comments</comments>
		<pubDate>Thu, 16 Jun 2011 21:32:34 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[cfml]]></category>
		<category><![CDATA[development]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1698</guid>
		<description><![CDATA[Recursion is a very powerful technique, but when processing certain types of data structures you can end up with problems.Â  And not the "compiler error" kind of problems, the bad semantic kind.Â  Fortunately, at least one class of them is pretty easy to solve with visit tracking, though it's a subtle solution.Â  But I'm going [...]]]></description>
			<content:encoded><![CDATA[<p>Recursion is a very powerful technique, but when processing certain types of data structures you can end up with problems.Â  And not the "compiler error" kind of problems, the bad semantic kind.Â  Fortunately, at least one class of them is pretty easy to solve with visit tracking, though it's a subtle solution.Â  But I'm going to build up to that from bare-bones recursion first.</p>
<p>As you know, a recursive algorithm is one which splits the problem into two parts: the base case and the recursive case.Â  The base case is a fixed solution to a simple form of the problem the algorithm is designed to solve.Â  The recursive case, on the other hand, is a dynamic solution which is defined as "a little work plus the solution to a simpler form of the same problem".Â  That simpler form of the problem is solved by reinvoking the recursive algorithm, and eventually reaches the base case (which breaks the cycle).Â  Consider computing factorials, which is the de facto standard for discussing recursion:</p>
<pre>function factorial(n) {
  if (n &lt; 0)
    throw 'NegativeArgumentException'
  return <span style="color: #ff0000;">n == 0 || n == 1</span> ? <span style="color: #008000;">1</span> : <span style="color: #0000ff;">n * </span><span style="color: #3366ff;">factorial(n - 1)</span>
}
</pre>
<p>Here the <span style="color: #ff0000;">conditional</span> is used to pick between the <span style="color: #008000;">base case</span> and the <span style="color: #0000ff;">recursive case</span>.Â  As you can see, the recursive case is defined by a <span style="color: #0000ff;">small bit of work</span> (multiplying by 'n') and then <span style="color: #3366ff;">solving a simple form of the problem</span> (the factorial of n &#8211; 1).Â  The most important thing to note is that the recursive case is guaranteed to eventually recurse down to the base case (or raise an exception).Â  That means infinite recursion is impossible, which is good.Â  Now onward to the interesting stuff&#8230;</p>
<p>Consider this data structure:</p>
<pre>barney = {
  "name": "Barney",
  "sex": "male",
  "dob": "1980-06-10",
  "children": [
    {
      "name": "Lindsay",
      "sex": "female",
      "dob": "2004-01-09",
      "children": []
    },
    {
      "name": "Emery",
      "sex": "male",
      "dob": "2005-08-12",
      "children": []
    }
  ]
}
</pre>
<p>This is a hierarchical data structure (i.e., a tree), where there is a single root node (the structure named "Barney") and then some number of child nodes.Â  We can see here that there are only two levels ("Barney" and "Barney's children"), but you can imagine here in 40 years that there will very likely be at least one more level, probably two more.Â  The point is that we don't know how deep the structure is, but we do know that it isn't infinitely deep.Â  It has to end somewhere.</p>
<p>How could I find the average age of everyone?Â  Well, I'd use recursion to do it, but if you think about what we need to do, you'll quickly see there's a problem.Â  Computing an average has to be done after all the aggregation is complete &#8211; you can't average subaverages and have it come out right unless you also weight the subaverages.Â  So we'll need to either sum the total age and divide by the number of people or compute subaverages and track the number of people "within" them.Â  I'm going to take the first approach.Â  Here are my functions:</p>
<pre>function averageAgeInSeconds(family) {
  var <span style="color: #008000;">result = {
    totalAge: 0,
    count: 0
  }</span>
  averageAgeParts(family, <span style="color: #008000;">result</span>)
  return Math.round(<span style="color: #008000;">result.totalAge</span> / <span style="color: #008000;">result.count</span>)
}
function averageAgeParts(family, <span style="color: #008000;">result</span>) {
  for (var i = 0, l = family.length; i &lt; l; i++) {
    <span style="color: #008000;">result.totalAge</span> += getAgeInSeconds(family[i])
    <span style="color: #008000;">result.count</span> += 1
    <span style="color: #3366ff;">averageAgeParts(family[i].children, <span style="color: #008000;">result</span>)</span>
  }
}
function getAgeInSeconds(person) {
  return Math.floor((new Date().valueOf() - Date.parse(person.dob)) / 1000)
}
</pre>
<p>As you can see, I'm creating a '<span style="color: #008000;">result</span>' structure for storing my aggregates, then using the 'averageAgeParts' helper function which is where the <span style="color: #3366ff;">recursion </span>happens.Â  After the aggregates are done, I'm doing the division in the main function to get the average.Â  Note that I don't have an explict base case anywhere.Â  The reason is that it's impossible for the data structure to be infinitely deep; I'm relying on that fact to act as my base case and let the recursion bottom out.Â  In more direct terms, at some point the loop inside averageAgeParts will be traversing an empty array (e.g., Lindsay's children), which means the recursive call will not be invoked (the base case).</p>
<p>Just for reference, the average age is about 14.7 years.</p>
<p>The important thing to note here is that my main function isn't directly recursive.Â  Instead it delegates to a recursive helper method and supplies some additional context (the '<span style="color: #008000;">result</span>' variable) for it to operate on while wakling the tree.Â  This extra stuff is critical for solving a lot of problems with recursion.Â  I'm not going to show the implementation, but consider how you'd change this so you could request the average age of a family, but constrain it to only females (be careful to ensure you count female descendents of males).Â  How about if you wanted to allow counting a certain number of generations (regardless of the total tree depth)?</p>
<p>Now on to the whole point: visitation.Â  The data structure I've shown to this point is a tree, as we discussed.Â  But it doesn't represent the real world: a child has two parents, not one.Â  That's no longer hierarchical, so we can't stick with a tree, we need to generalize into a graph.Â  (Just to be clear, trees <em>are</em> graphs, but with the extra constraint of hierarchy.)Â  So how might our structure look now?Â  First of all, we can't represent it with a literal; we'll need to define a tree structure first and then add some extra linkages to "break" the tree nature and revert it to just a graph.Â  Using the 'barney' object from above, here's how our graph might look.</p>
<pre>boisverts = [
  barney,
  {
    "name": "Heather",
    "sex": "female",
    "dob": "1980-02-12",
    "children": Array.concat(barney.children)
  }
]
</pre>
<p>The last line creates a 'children' array for Heather which contains the same contents as Barney's 'children' array (but is a separate array).Â  Now we can access Emery as either boisverts[<strong>0</strong>].children[1] or as boisverts[<strong>1</strong>].children[1] and it's the same object.Â  That's important: it's what makes this a graph instead of a tree.Â  So now what happens when we run our averageAgeInSeconds function on 'boisverts'?Â  It'll run just dandy, but while the correct answer is 18.9 years, the result will be 14.8 years.Â  The reason is that it'll count Emery and Lindsay twice (once as Barney's children and again as Heather's children).</p>
<p>What we need is some way to keep track of what we've already processed (visited) so we can avoid processing stuff multiple times, and we can do that with a new subkey in our existing 'result' structure:</p>
<pre>function averageAgeInSeconds(family) {
  var result = {
    totalAge: 0,
    count: 0<span style="color: #008000;">,
    visited: []</span>
  }
  averageAgeParts(family, result)
  return Math.round(result.totalAge / result.count)
}
function averageAgeParts(family, result) {
  for (var i = 0, l = family.length; i &lt; l; i++) {
    <span style="color: #008000;">if (visit(family[i], result)) {</span>
      result.totalAge += getAgeInSeconds(family[i])
      result.count += 1
      averageAgeParts(family[i].children, result)
    <span style="color: #008000;">}</span>
  }
}
<span style="color: #008000;">function visit(o, result) {
  for (var i = 0, l = result.visited.length; i &lt; l; i++) {
    if (result.visited[i] === o) {
      return false
    }
  }
  result.visited.push(o)
  return true
}</span>
</pre>
<p>What I've done is create a 'visit' function to keep track of which objects have been visited.Â  Now 'averageAgeParts' uses 'visit' to check if it should visit (process) an object.Â  I've implemented 'visit' with an array as I wanted illustrative clarity not performance.Â  <span style="text-decoration: underline;">In the real world you'd use a hashtable instead of an array so you'll have O(n) peformance instead of O(n</span><sup>2</sup><span style="text-decoration: underline;">).<br />
</span></p>
<p>Now when we run this we'll get 18.9 years, as expected, because even though Emery and Lindsay will be iterated over twice, they'll only be processed the first time.Â  More specifically, the 'visit' function will return false on the second pass so the body of the loop will be skipped and their values won't be added to the aggregates the second time.</p>
<p>This sort of problem crops up all the time in computer science, and while I've shown it here with recursion, it's not necessarily tied to recursive algorithms.Â  One of the ones which is quite common, and fortunately "below the radar" for a lot of people, is garbage collection in modern virtual machines.Â  Another place is with serializing objects, either for permanent storage or transport across the wire.Â  The specific solutions are different, but they all end up using some sort of visit tracking on the graph nodes or edges.</p>
<p><strong>NB:</strong> This post was prompted by a discussion on the <a href="http://groups.google.com/group/taffy-users">Taffy users mailing list</a>, but as it has myriad other implications, I've discussed the topic in a more general way.</p>
<p><strong>NB:</strong> All code is JavaScript, and should run in any reasonable environment.Â  I did all my testing in Firefox 3.5, but I'd expect this to work back in Netscape 4.7 and IE 4.</p>
<div id="_mcePaste" style="position: absolute; left: -10000px; top: 0px; width: 1px; height: 1px; overflow: hidden;">http://groups.google.com/group/taffy-users</div>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/06/16/visiting-recursion/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>cf.objective Tomorrow!</title>
		<link>https://www.barneyb.com/barneyblog/2011/05/10/cf-objective-tomorrow/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/05/10/cf-objective-tomorrow/#comments</comments>
		<pubDate>Tue, 10 May 2011 22:33:41 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[cfml]]></category>
		<category><![CDATA[coldfusion]]></category>
		<category><![CDATA[development]]></category>
		<category><![CDATA[personal]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1661</guid>
		<description><![CDATA[Tomorrow I'll be in Minneapolis for the 2011 iteration of cf.objective.Â  If you've looked at the schedule, you'll know I'm speaking twice: one about progressive enhancement first thing Thursday morning after the keynote, and again Friday right after lunch about migrating your apps to the cloud.Â  You'll also know that there are a hell of [...]]]></description>
			<content:encoded><![CDATA[<p>Tomorrow I'll be in Minneapolis for the 2011 iteration of <a href="http://www.cfobjective.com/">cf.objective</a>.Â  If you've looked at the schedule, you'll know I'm speaking twice: one about progressive enhancement first thing Thursday morning after the keynote, and again Friday right after lunch about migrating your apps to the cloud.Â  You'll also know that there are a hell of a lot of good sessions, so I'm really excited about it.</p>
<p>As is often the case with internet folks, I know a whole lot of people by name and/or handle, but far fewer in person.Â  So if you see me &#8211; I'm the tall, furry guy who isn't Andy Matthews &#8211; please say hello.Â  And as if you didn't need even more incentive, the company I work (Mentor Graphics Corp) for is currently looking to hire a couple more full-time developers out in beautiful Portland, OR.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/05/10/cf-objective-tomorrow/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Minor Shortcodes Update</title>
		<link>https://www.barneyb.com/barneyblog/2011/01/21/minor-shortcodes-update/</link>
		<comments>https://www.barneyb.com/barneyblog/2011/01/21/minor-shortcodes-update/#comments</comments>
		<pubDate>Sat, 22 Jan 2011 01:11:19 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[cfml]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1628</guid>
		<description><![CDATA[Last winter I released a CFML port of the WordPress Shortcodes functionality.Â  It's proven both very useful and very flexible in the real world, exactly as I'd hoped it would.Â  I tried to make a very direct port of the PHP, but after using it on a number of projects, I found myself reimplementing the [...]]]></description>
			<content:encoded><![CDATA[<p>Last winter I released a CFML port of the WordPress Shortcodes functionality.Â  It's proven both very useful and very flexible in the real world, exactly as I'd hoped it would.Â  I tried to make a very direct port of the PHP, but after using it on a number of projects, I found myself reimplementing the same wrapper functionality, so I decided to move that into the core.</p>
<p>The mods are very simple, just the addition of an 'addAll' method.Â  Consider this code:</p>
<pre>sc = new shortcodes.shortcodes();
sc.add("time", new shortcodes.time());
sc.add("latex", new shortcodes.latex());
</pre>
<p>It can now be expressed like this:</p>
<pre>sc = new shortcodes.shortcodes();
sc.addAll({
  'time': new shortcodes.time(),
  'latex': new shortcodes.latex()
});
</pre>
<p>The really handy part, however, is that there is also a 'setShortcodes' alias for the 'addAll' method, so you can use it within setter-injection frameworks (e.g., ColdSpring).Â  So you can configure your shortcode engine with your DI framework of choice without needing an intermediary as you've had to use to this point:</p>
<pre>&lt;bean id="shortcodes" class="shortcodes.shortcodes"&gt;
  &lt;property name="shortcodes"&gt;
    &lt;map&gt;
      &lt;entry key="time"&gt;
        &lt;bean class="shortcodes.time" /&gt;
      &lt;/entry&gt;
      &lt;entry key="latex"&gt;
        &lt;bean class="shortcodes.latex" /&gt;
      &lt;/entry&gt;
    &lt;/map&gt;
  &lt;/property&gt;
&lt;/bean&gt;
</pre>
<p>As you might imagine, this allows you to use your normal DI infrastructure to configure your shortcode implementations, which is a huge boon.Â  The ColdSpring (and Spring) syntax for maps is a little verbose, but it's not too bad, and it lets you keep all your wiring in one place without resorting to wrapper beans or weird factory setups.</p>
<p>So no functional changes to the actual implementation, just easier to use.Â  I've also bundled 'time' and 'date' shortcode implementations to go along with the original <img src='http://s.wordpress.com/latex.php?latex=%5CLaTeX&#038;bg=T&#038;fg=000000&#038;s=0' alt='\LaTeX' title='\LaTeX' class='latex' /> implementation.Â  They're much more simplistic, which will hopefully do a better job of helping people get started implementing their own shortcodes (as well as providing some more utility out of the box for integrators).</p>
<p>As always, for full details and up-to-date info you should visit the <a href="http://www.barneyb.com/barneyblog/projects/shortcodes/">project page</a>, check out the <a href="http://www.barneyb.com/r/shortcodes/">online demo</a>, or grab <a href="https://ssl.barneyb.com/svn/barneyb/shortcodes/trunk">source </a>from Subversion.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2011/01/21/minor-shortcodes-update/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Minor AmazonS3.cfc Bug Fix</title>
		<link>https://www.barneyb.com/barneyblog/2010/09/02/minor-amazons3-cfc-bug-fix/</link>
		<comments>https://www.barneyb.com/barneyblog/2010/09/02/minor-amazons3-cfc-bug-fix/#comments</comments>
		<pubDate>Thu, 02 Sep 2010 22:10:49 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[amazon]]></category>
		<category><![CDATA[cfml]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1578</guid>
		<description><![CDATA[Today I identified a subtle bug with the listObjects method of AmazonS3.cfc dealing with delimiters.Â  If you supply a prefix that ends with a trailing delimiter, certain paths would be returned partially truncated.Â  Removing the trailing delimiter solves the issue, so there's an easy workaround, but I've added a snippet to take care of that [...]]]></description>
			<content:encoded><![CDATA[<p>Today I identified a subtle bug with the listObjects method of AmazonS3.cfc dealing with delimiters.Â  If you supply a prefix that ends with a trailing delimiter, certain paths would be returned partially truncated.Â  Removing the trailing delimiter solves the issue, so there's an easy workaround, but I've added a snippet to take care of that if you inadvertantly pass one in.Â  The patched CFC <a href="https://ssl.barneyb.com/svn/barneyb/!svn/bc/6774/amazon/trunk/amazons3.cfc">is available here</a>.Â  You can always get the latest version on <a href="http://www.barneyb.com/barneyblog/projects/amazon-s3-cfc/">the project page</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2010/09/02/minor-amazons3-cfc-bug-fix/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>On-The-Fly YUI Compressor</title>
		<link>https://www.barneyb.com/barneyblog/2010/08/03/on-the-fly-yui-compressor/</link>
		<comments>https://www.barneyb.com/barneyblog/2010/08/03/on-the-fly-yui-compressor/#comments</comments>
		<pubDate>Wed, 04 Aug 2010 01:35:14 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[cfml]]></category>
		<category><![CDATA[javascript]]></category>
		<category><![CDATA[tools]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1570</guid>
		<description><![CDATA[A couple years ago I wrote about using YUI Compressor to do built-time aggregation and compression of static assets.Â  That works well and good if you have a build environment, but that's not always the case.Â  So, still using YUI Compressor, I set up a simple script that'll do runtime aggregation and compression of assets [...]]]></description>
			<content:encoded><![CDATA[<p>A couple years ago <a href="http://www.barneyb.com/barneyblog/2008/04/08/build-time-aggregation-of-jscss-assets/">I wrote about</a> using <a href="http://developer.yahoo.com/yui/compressor/">YUI Compressor</a> to do built-time aggregation and compression of static assets.Â  That works well and good if you have a build environment, but that's not always the case.Â  So, still using YUI Compressor, I set up a simple script that'll do runtime aggregation and compression of assets using my favorite mod_rewrite-based file caching mechanism.</p>
<p>The basic idea is that your HTML includes a reference to "agg_standard.js", which is an alias for whatever JS files you need for your standard user (as opposed to a mobile user, for example).Â  That request comes to your server and if the file exists, gets served back like any other JS file.Â  If it <em>doesn't</em> exist, however, mod_rewrite will forward it to a CFM page to generate it:</p>
<pre>RewriteCondÂ Â Â Â  %{REQUEST_FILENAME}Â Â Â Â  !-s
RewriteRuleÂ Â Â Â  (/my_app)/static/(agg_.*)\.js$ $1/aggregator.cfm?name=$2.js
</pre>
<p>In our example, the request passed to aggregator.cfm would have "agg_standard.js" as the 'name' attribute, which is how we'll figure out what we need to aggregate together:</p>
<pre>switch (url.name) {
case "<span style="color: #0000ff;">agg_standard.js</span>":
  files = [
    "jquery/jquery" &amp; (request.isProduction ? ".min" : ""),
    "jquery/jquery.ui" &amp; (request.isProduction ? ".min" : ""),
    "yui/yahoo-min",
    "yui/event-min",
    "yui/history-min",
    "util",
    "jscalendar/calendar",
    "jscalendar/lang/calendar-en",
    "jscalendar/calendar-setup",
    "dracula/raphael-min",
    "dracula/graffle",
    "dracula/graph"
  ];
  break;
case "agg_iphone.js":
  files = [
    "jquery/jquery" &amp; (request.isProduction ? ".min" : ""),
    "yui/yahoo-min",
    "yui/event-min",
    "yui/history-min",
    "util"
  ];
  break;
}
</pre>
<p>The important bits, of course, is the Groovy script that actually does the aggregation and compression.Â  It uses YUI Compressor, so you'll need to have the yiucompressor-x.y.z.jar file on your classpath (probably in /WEB-INF/lib).Â  Here it is:</p>
<pre>import com.yahoo.platform.yui.compressor.*

sw = new StringWriter()
<span style="color: #0000ff;">variables.files</span>.each {
  def f = new File(variables.STATIC_DIR + it + '.js')
  sw.append('/* ').append(f.name).append(' */\n')
  if (! it.endsWith(".min") &amp;&amp; ! it.endsWith("-min")) {
    def compressor = new JavaScriptCompressor(f.newReader(), null)
    compressor.compress(sw, -1, false, false, false, false)
  } else {
    sw.append(f.text.trim())
  }
  sw.append('\n')
}
variables.buffer = sw.toString()</pre>
<p>Pretty straightforward: it just loops over the files, using a StringWriter to build up the aggregated buffer.Â  Each file is either compressed into the Writer or simply appended based on whether the file has already been minified (based on ".min" or "-min" in the filename).Â  Each file also gets a comment label in the Writer above it's contents so that the aggregated file is a little easier to parse (at the expense of a few extra bytes).Â  Once done, the Writer's contents are stored in the 'buffer' variable caching on the filesystem:</p>
<pre>&lt;cfset fileWrite(STATIC_DIR &amp; url.name, buffer) /&gt;
&lt;cflocation url="#url.name#" addtoken="false" /&gt;</pre>
<p>You'll notice that I'm not streaming the buffer back out to the user, but instead 302-ing back to the same URL.Â  This is important.Â  The reason is that Apache does a whole bunch of stuff to optimize static assets, and if I serve the content back with CFCONTENT, I'll miss out on all of that.Â  Yes, the 302 has a little bit of overhead on the initial page load, but it reduces the total transfer size by several hundred KB (because of the GZIPping), and avoids a rerequest on the next page load (because of the cache headers).Â  So it's completely worth it, the moreso because this is an application likely to generate extended usage rather than a content-centric site that is likely to see single-page "bounce" visits from search engines.</p>
<p>The last piece of the puzzle is handling versioning of your assets.Â  When you change your JS file, you necessarily have to invalidate your cache (by deleting the files) so the aggregated version can be rebuilt with the new JS.Â  The easiest way to do that is to use psuedo-versioning of your assets.Â  You'll see a lot of sites will add a timestamp or a version number to their files (e.g., "arc/yui/reset_2.6.5.css" from Yahoo.com) so that when the update the file it gets a new filename, and is therefore redownloaded by everyone (because it doesn't exist in their cache).Â  That's great, but it means you have to rename your files all the time which is kind of a pain.Â  But you can fake it:</p>
<pre>&lt;cfset STATIC_ASSET_VERSION = 15 /&gt;
&lt;script type="text/javascript" src="static/agg_standard_#STATIC_ASSET_VERSION#.js"&gt;&lt;/script&gt;
</pre>
<p>That'll generate a request to "agg_standard_15.js", as you might imagine, which isn't going to work so well.Â  But we can just change the 'switch' line from the first snippet to this:</p>
<pre>switch (REReplace(url.name, "^(agg_.*?)(_[0-9]+)?\.js$", "\1.js")) {</pre>
<p>Now it'll strip out that "fake" number string and switch on just "agg_standard.js", which is what we want.Â  But that 'fileWrite' call later will still use the full filename (with the number embedded).Â  That way subsequents will get the filesystem cacheing, the headers and GZIPping from Apache, and all the other love.Â  And when you rev your files, you need only increment the STATIC_ASSET_VERSION variable and you'll have a brand new set of virtual URLs for all your assets, no fuss, no muss.</p>
<p>Oh, and just in case you're wondering, the aggregation and compression is <em>fast</em>.Â  If you've ever used the command line or Ant task, you might fear that it's slow, but most of the time you see there is from the JVM spinning up, not the actual compression.Â  Since this is all in-JVM, you don't pay any of that cost.Â  It's certainly not fast enough to have it run in production on every request (hence the file caching), but it's totally reasonable to do on your production box as part of deploying a new version of the app.Â  It's also probably fast enough to have running every request on your internal test/staging boxes, though that'll depend on how much you're aggregating/compressing among other things.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2010/08/03/on-the-fly-yui-compressor/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Flash Scope CFC</title>
		<link>https://www.barneyb.com/barneyblog/2010/07/30/flash-scope-cfc/</link>
		<comments>https://www.barneyb.com/barneyblog/2010/07/30/flash-scope-cfc/#comments</comments>
		<pubDate>Sat, 31 Jul 2010 02:50:34 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[cfml]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1563</guid>
		<description><![CDATA[If you've ever used Grails, you probably know about the 'flash' scope that it provides for passing data from one request to the next.Â  Certainly not a must-have feature, but it's quite handy.Â  The typical use case is after a form submit, you set stuff into the flash scope and then redirect to some page [...]]]></description>
			<content:encoded><![CDATA[<p>If you've ever used Grails, you probably know about the 'flash' scope that it provides for passing data from one request to the next.Â  Certainly not a must-have feature, but it's quite handy.Â  The typical use case is after a form submit, you set stuff into the flash scope and then redirect to some page where you use the flash scope's contents to spit a message back to the user.Â  For example, after editing a user and submitting, you'd redirect the browser to the user listing page and display a "Successfully updated user" message at the top of the page.</p>
<p>The benefit of using the flash scope for doing this is that it only lasts one request, so if the user refreshes the listing page, they won't see the success message again (nor should they, since a user <em>wasn't</em> updated on the refresh).Â  It also keeps your URLs clean (because you don't have to pass stuff along on the query string), and lets you pass complex data (since there is no serialization).</p>
<p>I'm sure a lot of people have faked this functionality in some sort of use-specific way, and I know I'm guilty.Â  However, after preparing to do it yet again, I thought I'd build a more generic mechanism that I could reuse.Â  Before I give you the actual code, here's how you might use it:</p>
<pre>&lt;cfcase value="onRequestStart"&gt;
  &lt;cfset <span style="color: #0000ff;">flashScope = createObject("component", "FlashScope")</span> /&gt;
&lt;/cfcase&gt;

&lt;cfcase value="updateGoal"&gt;
  &lt;cfset xfa.success = "goalList" /&gt;
  &lt;cfset goalService = request.beanFactory.getBean("goalService") /&gt;
  &lt;cfset goalService.updateGoal(
    session.user.getId(),
    attributes.id,
    attributes.name,
    attributes.definition
  ) /&gt;
  &lt;cfset <span style="color: #0000ff;">flashScope.put("message", "Goal Updated Successfully!")</span> /&gt;
  &lt;cfset location("#self##xfa.success#") /&gt;
&lt;/cfcase&gt;

&lt;cfcase value="goalList"&gt;
  &lt;cfset goalService = request.beanFactory.getBean("goalService") /&gt;
  &lt;cfset goalList = goalService.getGoals(session.user.getId()) /&gt;
  &lt;cfif <span style="color: #0000ff;">flashScope.has("message")</span>&gt;
    &lt;cfset statusMessage = <span style="color: #0000ff;">flashScope.get("message")</span> /&gt;
  &lt;/cfif&gt;
  &lt;cfset include("dsp_goallist", "bodyContent") /&gt;
  &lt;cfset do("lay_auto") /&gt;
&lt;/cfcase&gt;</pre>
<p>As you can see, we're instantiating the flash scope at the start of every request.Â  Then in 'updateGoal', the 'message' key is set into the flash scope.Â  Finally in 'goalList', if there's a 'message' in the flash scope, it is set into a local variable (to be emitted within dsp_goallist.cfm).Â  Pretty straightforward.Â  The actual mechanism is a pair of structures: one in the request scope for incoming variables and one in the session scope for outgoing variables.Â  The FlashScope CFC is nothing more than a facade to those two structures.Â  This implies that you must have the session scope available to your application, of course.</p>
<p>I haven't created a full-on project for this but it garners sufficient interest and development activity, I'll certainly do that.Â  In the meantime you can <a href="https://ssl.barneyb.com/svn/barneyb/eventlog/trunk/lib/flash_scope/FlashScope.cfc">view/download the CFC</a> (or if you're using Subversion,<a href="https://ssl.barneyb.com/svn/barneyb/eventlog/trunk/lib/flash_scope/"> svn:externals it</a> into your project).</p>
<p>In the interest of full disclosure, I want to call out two shortcomings in the implementation, both of which are intentional (to minimize complexity):</p>
<ol>
<li>There is no respect paid to dealing with concurrent requests.Â  They'll happily intermix their variables in the flash scope, potentially causing errors and/or bizarre behaviour.</li>
<li>The scope isn't really tied to the 'next' request, it's tied to the 'next request that reads the flash scope'.Â  This can yield behaviour if your app assumes that the flash scope will be consumed, but it isn't for whatever reason.</li>
</ol>
<p>Both of these would require some sort of nonce on every request, which adds a whole layer of complexity and necessitates passing some sort of request parameter on the URL or whatever.Â  That's a) a mess, b) complicated, and c) application-specific.Â  As such, I've opted to intentionally <strong>not</strong> handle those cases since they're edge cases and I'd rather keep things simple.Â  That and I have this inexplicable obsession with 80-100 line, single-file microframeworks (see <a href="http://www.barneyb.com/barneyblog/projects/fb3-lite/">FB3lite</a>, <a href="http://www.barneyb.com/barneyblog/projects/cfgroovy2/">CFGroovy</a>, <a href="http://www.barneyb.com/barneyblog/projects/transaction-advice/">TransactionAdvice</a>, etc.).Â  :)</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2010/07/30/flash-scope-cfc/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Closures, Closures, Closures</title>
		<link>https://www.barneyb.com/barneyblog/2010/07/23/closures-closures-closures/</link>
		<comments>https://www.barneyb.com/barneyblog/2010/07/23/closures-closures-closures/#comments</comments>
		<pubDate>Fri, 23 Jul 2010 19:56:14 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[cfml]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1559</guid>
		<description><![CDATA[Guess what time it is, kids!!
It's "Barney still wants CFML closures" time!Â  Yay!
Today's impetus is Edmund, Sean Corfield's event driven programming framework.Â  In order to register event listeners, you have to a CFC instance with a specific method to be invoked on it, and which accepts an Edmund Event as it's sole argument.Â  Which means [...]]]></description>
			<content:encoded><![CDATA[<p>Guess what time it is, kids!!</p>
<p>It's "Barney still wants CFML closures" time!Â  Yay!</p>
<p>Today's impetus is <a href="http://edmund.riaforge.org/">Edmund</a>, <a href="http://corfield.org/">Sean Corfield</a>'s event driven programming framework.Â  In order to register event listeners, you have to a CFC instance with a specific method to be invoked on it, and which accepts an Edmund Event as it's sole argument.Â  Which means you have to have these silly little CFCs hanging about that simply get the event and then hand it off to the appropriate business components to actually do stuff.Â  Yes, I understand that's a very OO way to do it: lots of little, purpose-specific types passing messages between them.Â  But it's a bitch with CFML because every type has to be it's own file and you don't get context inheritance.</p>
<p>In Java most of the time your event listeners are anonymous inner classes &#8211; instances of classes that are defined inline.Â  That mechanism gets a lot of grief, and while I agree that it's a little more verbose than necessary in simple cases, it's a lot better than the crap that static typing and/or checked exceptions foists on you, and having a full class definition can be useful as things get more complex.</p>
<p>Groovy smoothes that a little by allowing you to define Closures and use them instead.Â  Under the hood, it's just converting them to your standard Java anonymous inner classes, but the syntax is rather nicer.Â  If you know Ruby, think lambdas.</p>
<p>This is what I'd do in Java:</p>
<pre>edmund.register("eventCreated", new GenericHandler() {
  public handleEvent(Event e) {
    <span style="color: #0000ff;">beanFactory.getBean("eventservice").postprocessEvent(e.value("eventId"));</span>
  }
});
</pre>
<p>or in Groovy:</p>
<pre>edmund.register("eventCreated", {
  <span style="color: #0000ff;">beanFactory.getBean("eventservice").postprocessEvent(e.value('eventId'))</span>
})
</pre>
<p>But in CFML, I need to create a separate type (in a separate file):</p>
<pre>&lt;cfcomponent&gt;

  &lt;cffunction name="init"&gt;
    &lt;cfargument name="beanFactory" /&gt;
    &lt;cfset variables.beanFactory = arguments.beanFactory /&gt;
  &lt;/cffunction&gt;

  &lt;cffunction name="handleEvent"&gt;
    &lt;cfargument name="e" /&gt;
    <span style="color: #0000ff;">&lt;cfset beanFactory.getBean("eventservice").postprocessEvent(e.value("eventId")) /&gt;</span>
  &lt;/cffunction&gt;

&lt;/cfcomponent&gt;
</pre>
<p>And then register it like this:</p>
<pre>&lt;cfset edmund.register("eventCreated",
  createObject("component", "MyEventHandler").init(beanFactory)
) /&gt;
</pre>
<p>What a mess.Â  Not only do I have to create a separate file to have that one single line of code in it (line 10), but I also have to worry about passing context around (in this case, the beanFactory), because the CFC instance doesn't inherit the context it's instantiated in (as closures and anonymous inner types do).Â  And this is a ridiculously trivial example.Â  Here's what I'd like to see in CFML:</p>
<pre>edmund.register("eventCreated", function(e) {
  <span style="color: #0000ff;">beanFactory.getBean("eventservice").postprocessEvent(e.value("eventId"));</span>
});
</pre>
<p>You can see that I'm following the ECMAScript-like nature of CFML expressions and stealing ECMAScript's function literal syntax (one of them, at least) to create an anonymous function.Â  In order for this to work, it'd have to be a closure (as ECMAScript functions are), not just a context-free function.Â  As an alternative (which is more complicated, but which has certain advantages in certain scenarios), would be to have a CFC literal (anonymous inner CFC) as well:</p>
<pre>edmund.register("eventCreated", new component() {
  function handleEvent(e) {
    <span style="color: #0000ff;">beanFactory.getBean("eventservice").postprocessEvent(e.value("eventId"));</span>
  }
});
</pre>
<p>Personally, I'd much prefer the closure if I only got one, but both would be nice.Â  The semantics of anonymous inner types can be a little wonky, and the advantages over simple closures is small, but they can still be really useful.Â  Now that we have a CFSCRIPT-based way of defining components, the language at least has syntactic constructs to express anonymous types, so it's theoretically possible.</p>
<p>So since I can't do all this neat stuff, you might ask what I did do.Â  I used ColdSpring with a purpose-sepecific adapter I wrote, along with a custom extension to Edmund to allow registering listeners from ColdSpring.Â  So my Edmund config looks like this:</p>
<pre>&lt;bean id="edmund"&gt;
  &lt;constructor-arg name="asyncByDefault"&gt;&lt;value&gt;false&lt;/value&gt;&lt;/constructor-arg&gt;
  &lt;property name="eventListeners"&gt;
    &lt;map&gt;
      &lt;entry key="eventCreated"&gt;
        &lt;bean class="edmund.framework.ColdSpringListener"&gt;
          &lt;constructor-arg name="handler"&gt;&lt;ref bean="eventservice" /&gt;&lt;/constructor-arg&gt;
          &lt;constructor-arg name="method"&gt;&lt;value&gt;postprocessEvent&lt;/value&gt;&lt;/constructor-arg&gt;
        &lt;/bean&gt;
     &lt;/entry&gt;
  &lt;/property&gt;
&lt;/bean&gt;</pre>
<p>The ColdSpringListener bean simply takes care of delegating the configured method to the supplied bean when it is triggered, passing along the event arguments along.Â  This obviously is still pretty verbose, but the per-listener overhead is four lines of XML (in an existing file), not a 13 line CFC (in a new file).Â  That counts as a win by me.Â  The way I extended Edmund allowed passing a single listener or an array of listeners.Â  The method I added is here:</p>
<pre>&lt;cffunction name="setEventListeners" returntype="any" access="public" output="false"
  hint="I register multiple new listeners at once.Â  I DO NOT REPLACE listeners,
    despite being a setter.Â  I should be named addMultipleEventListeners, but must
    be named setEventListeners so I can be used from ColdSpring."&gt;
  &lt;cfargument name="listeners" type="struct" required="true"
    hint="I am a struct with event names as keys and either a single listener or
      array of listeners as values.Â  Note that there is no way to specify the
      handler method or whether listeners are asynchronous." /&gt;
  &lt;cfset var e = "" /&gt;
  &lt;cfset var i = "" /&gt;
  &lt;cfloop collection="#listeners#" item="e"&gt;
    &lt;cfif NOT isArray(listeners[e])&gt;
      &lt;cfset i = listeners[e] /&gt;
      &lt;cfset listeners[e] = arrayNew(1) /&gt;
      &lt;cfset arrayAppend(listeners[e], i) /&gt;
    &lt;/cfif&gt;
    &lt;cfloop from="1" to="#arrayLen(listeners[e])#" index="i"&gt;
      &lt;cfset register(e, listeners[e][i]) /&gt;
    &lt;/cfloop&gt;
  &lt;/cfloop&gt;
  &lt;cfreturn this /&gt;
&lt;/cffunction&gt;</pre>
<p>So is this good stuff?Â  It's OK.Â  It's solid for a CFML solution.Â  But there is no question that it pales in comparison to the elegance with which you could solve the problem in other languages.Â  Java (same age as CFML) has this, Ruby (a little older than CFML) has this, Python (much older than CFML) has this, and don't even get me started on Lisp (more than double the age of CFML).Â  It's not a concept new to computer science.</p>
<p>Alright.Â  Rant over.Â  Until next time I seriously consider rebuild huge swaths of CFML in Groovy just for closures and start yelling again.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2010/07/23/closures-closures-closures/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Amazon S3 CFC Update &#8211; Now With Listings!</title>
		<link>https://www.barneyb.com/barneyblog/2010/06/08/listings-for-amazon-s3-cfc/</link>
		<comments>https://www.barneyb.com/barneyblog/2010/06/08/listings-for-amazon-s3-cfc/#comments</comments>
		<pubDate>Tue, 08 Jun 2010 17:48:00 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[amazon]]></category>
		<category><![CDATA[cfml]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1543</guid>
		<description><![CDATA[I've added two new methods to my Amazon S3 CFC: listBuckets and listObjects.Â  Both of them do about what you'd expect, returning a CFDIRECTORY-esque recordset object containing the rows you are interested in.Â  I've attempted to make S3 appear like a "normal" filesystem where "/" is S3 itself, the top-level directories are your buckets, and [...]]]></description>
			<content:encoded><![CDATA[<p>I've added two new methods to my <a href="http://www.barneyb.com/barneyblog/projects/amazon-s3-cfc/">Amazon S3 CFC</a>: listBuckets and listObjects.Â  Both of them do about what you'd expect, returning a CFDIRECTORY-esque recordset object containing the rows you are interested in.Â  I've attempted to make S3 appear like a "normal" filesystem where "/" is S3 itself, the top-level directories are your buckets, and your objects are below that.Â  At the moment no consideration is made for paging or truncation.Â  Leveraging the new functionality, here's complete source for a simple S3 browser (minus your key/secret):</p>
<pre>&lt;cfparam name="url.path" default="" /&gt;

&lt;cfset s3 = createObject("component", "amazons3").init(
  "YOUR_AWS_KEY",
  "YOUR_AWS_SECRET"
) /&gt;

&lt;cfoutput&gt;
&lt;cfset bp = "" /&gt;
&lt;h1&gt;
&lt;a href="?path=#bp#"&gt;ROOT&lt;/a&gt;
&lt;cfloop list="#url.path#" index="segment" delimiters="/"&gt;
  &lt;cfset bp = listAppend(bp, segment, "/") /&gt;
  / &lt;a href="?path=#bp#"&gt;#segment#&lt;/a&gt;
&lt;/cfloop&gt;
&lt;/h1&gt;

&lt;cfif url.path EQ ""&gt;
  &lt;cfset b = s3.listBuckets() /&gt;
  &lt;ul&gt;
    &lt;cfloop query="b"&gt;
      &lt;li&gt;&lt;a href="?path=/#name#"&gt;#name#/&lt;/a&gt; #dateLastModified#&lt;/li&gt;
    &lt;/cfloop&gt;
  &lt;/ul&gt;
&lt;cfelse&gt;
  &lt;cfset q = s3.listObjects(listFirst(url.path, '/'), listRest(url.path, '/')) /&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;a href="?path=#reverse(listRest(reverse(url.path), '/'))#"&gt;..&lt;/a&gt;&lt;/li&gt;
    &lt;cfloop query="q"&gt;
      &lt;li&gt;
      &lt;cfif type EQ "dir"&gt;
        &lt;a href="?path=#listAppend(directory, name, '/')#"&gt;#name#/&lt;/a&gt;
      &lt;cfelse&gt;
        &lt;a href="#s3.s3Url(bucket, objectKey)#"&gt;#name#&lt;/a&gt;
      &lt;/cfif&gt;
      &lt;/li&gt;
    &lt;/cfloop&gt;
  &lt;/ul&gt;
&lt;/cfif&gt;
&lt;/cfoutput&gt;</pre>
<p>The default mode of operation assumes a delimiter of '/' (just like a filesystem).Â  If you want to do non-delimited operations (like generic prefix matching), you'll want to supply an empty delimiter, or you'll get weird results.Â  For example:</p>
<pre>&lt;cfset k_objects = s3.listObjects('my-bucket', 'k', '') /&gt;
</pre>
<p>If you omit the third parameter, the default 'k' will be used, and you'll get back objects within the 'k' psuedo-directory, rather than objects that begin with a 'k'.Â  This is the reverse of the default position of the raw S3 API, which assumes you want simple prefixing and makes you explicitly add the delimiter if you want psuedo-directory contents.</p>
<p>This dichotomy can also lead to weird results in the resulting recordset.Â  Every recordset comes with both 'bucket' and 'objectKey' columns that match the raw S3 nomenclature and 'directory' and 'name' columns that match the filesystem "view" of S3.Â  If you're doing raw prefixes you'll want to use bucket/objectKey (as the directory/name semantic doesn't work with prefixes).Â  If you're doing filesystem type stuff you'll probably want directory/name (though bucket/objectKey will still be correct).</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2010/06/08/listings-for-amazon-s3-cfc/feed/</wfw:commentRss>
		<slash:comments>17</slash:comments>
		</item>
		<item>
		<title>Tulsa CFUG Presentation (CFGroovy)</title>
		<link>https://www.barneyb.com/barneyblog/2010/05/26/tulsa-cfug-cfgroovy/</link>
		<comments>https://www.barneyb.com/barneyblog/2010/05/26/tulsa-cfug-cfgroovy/#comments</comments>
		<pubDate>Wed, 26 May 2010 15:35:35 +0000</pubDate>
		<dc:creator>barneyb</dc:creator>
				<category><![CDATA[cfml]]></category>
		<category><![CDATA[groovy]]></category>
		<category><![CDATA[personal]]></category>

		<guid isPermaLink="false">http://www.barneyb.com/barneyblog/?p=1526</guid>
		<description><![CDATA[Yesterday I presented CFGroovy to the Tulsa CFUG via Connect.Â  The recording is now available on Connect, thanks to Steve.Â  You can also grab the slidedeck (as a PDF) I skimmed through, and of course, the CFGroovy framework itself (project page, core SVN, demo SVN), including the demo files that I showed. Â Note that the [...]]]></description>
			<content:encoded><![CDATA[<p>Yesterday I presented CFGroovy to the Tulsa CFUG via Connect.Â  The recording is now <a href="http://adobechats.adobe.acrobat.com/p20888993/">available on Connect</a>, thanks to Steve.Â  You can also grab the <a href="http://www.barneyb.com/barneyblog/wp-content/uploads/2010/04/Polyglot_Programming.pdf">slidedeck (as a PDF)</a> I skimmed through, and of course, the CFGroovy framework itself (<a href="http://www.barneyb.com/barneyblog/projects/cfgroovy2/">project page</a>, <a href="https://ssl.barneyb.com/svn/barneyb/cfgroovy2/trunk/engine/">core SVN</a>, <a href="https://ssl.barneyb.com/svn/barneyb/cfgroovy2/trunk/demo/">demo SVN</a>), including the demo files that I showed. Â Note that the slidedeck is actually titled 'Polyglot Programming' and served as an introduction as to <em>why</em> you might want to use Groovy (or another language) before delving into <em>how</em> you'd actually do it.</p>
]]></content:encoded>
			<wfw:commentRss>https://www.barneyb.com/barneyblog/2010/05/26/tulsa-cfug-cfgroovy/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>
