<?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>Orange is my favorite color &#187; My Software</title>
	<atom:link href="http://www.ghidinelli.com/c/software/feed" rel="self" type="application/rss+xml" />
	<link>http://www.ghidinelli.com</link>
	<description></description>
	<lastBuildDate>Fri, 27 Jan 2017 17:45:50 +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>CFPAYMENT now on GitHub</title>
		<link>http://www.ghidinelli.com/2013/06/05/cfpayment-now-on-github</link>
		<comments>http://www.ghidinelli.com/2013/06/05/cfpayment-now-on-github#comments</comments>
		<pubDate>Wed, 05 Jun 2013 17:05:29 +0000</pubDate>
		<dc:creator>brian</dc:creator>
				<category><![CDATA[ColdFusion]]></category>
		<category><![CDATA[My Software]]></category>
		<category><![CDATA[Web/Internet]]></category>
		<category><![CDATA[cfpayment]]></category>

		<guid isPermaLink="false">http://www.ghidinelli.com/?p=1557</guid>
		<description><![CDATA[CFPAYMENT, the open source payment processing library for ColdFusion, is now hosted on GitHub for your forking pleasure]]></description>
			<content:encoded><![CDATA[<p>I see people in ColdFusion land still reinventing the wheel in writing payment gateways.  Stop!  Believe me when I tell you there are 502 ways for a payment request to fail and I&#8217;ve experienced every single one of them!  The open source payment processing library, CFPAYMENT, has processed tens of millions of dollars in production applications over the past 5 years.  Either use one of our supported gateways (including the very cool <a href="http://www.stripe.com">Stripe</a>) or write a tiny amount of code to translate requests from your gateway of choice.</p>
<p>To encourage you to leverage rather than rebuild, I&#8217;ve moved the source to GitHub where you can fork and pull:</p>
<p><a href="https://github.com/ghidinelli/cfpayment">https://github.com/ghidinelli/cfpayment</a></p>
<p>There&#8217;s also a <a href="https://groups.google.com/forum/?fromgroups#!forum/cfpayment">Google group</a> where I provide support if you want to use a gateway we don&#8217;t yet include.  I&#8217;ll help you understand how to use the library and extend it so you don&#8217;t go through the pain of learning the hard way how transactions can fail in production.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ghidinelli.com/2013/06/05/cfpayment-now-on-github/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Faster unit tests with ColdSpring and MXUnit</title>
		<link>http://www.ghidinelli.com/2013/03/27/faster-unit-tests-with-coldspring-and-mxunit</link>
		<comments>http://www.ghidinelli.com/2013/03/27/faster-unit-tests-with-coldspring-and-mxunit#comments</comments>
		<pubDate>Thu, 28 Mar 2013 00:07:32 +0000</pubDate>
		<dc:creator>brian</dc:creator>
				<category><![CDATA[ColdFusion]]></category>
		<category><![CDATA[My Software]]></category>
		<category><![CDATA[Web/Internet]]></category>
		<category><![CDATA[coldspring]]></category>
		<category><![CDATA[mxunit]]></category>
		<category><![CDATA[unit testing]]></category>

		<guid isPermaLink="false">http://www.ghidinelli.com/?p=1540</guid>
		<description><![CDATA[Make unit testing faster and more isolated by instructing Coldspring to only load the relevant beans for your test.]]></description>
			<content:encoded><![CDATA[<p>Nerd post today.  Most of my <a href="http://www.mxunit.org">MXUnit</a> unit tests look something like:</p>
<pre><code>&lt;cffunction name="setUp" returntype="void" access="public"&gt;
	&lt;cfscript&gt;
		variables.beanFactory = createObject("component", "coldspring.beans.DefaultXmlBeanFactory").init();
		variables.beanFactory.loadBeansFromXmlFile("/config/globalbeans.xml", true);
		variables.service = variables.beanFactory.getBean("BatchbookService");
	&lt;/cfscript&gt;

	&lt;cfset localMode = true /&gt;
&lt;/cffunction&gt;</code></pre>
<p>You can read more about my <a href="https://www.ghidinelli.com/2012/08/25/unit-testing-trick-developing-against-apis">localMode trick for testing remote APIs</a> but I&#8217;m simply loading up my Coldspring configuration file for my app which includes dozens and dozens and dozens of beans.  I have three problems with this:</p>
<ol>
<li>The unit tests depend on the configuration used to run the site which might not always be in my environment</li>
<li>It slows down my tests by generating beans that my unit test doesn&#8217;t need</li>
<li>My production and test bean config may not be the same (credentials, etc)</li>
</ol>
<p>I might be able to get around #2 by using lazy init but the principle still holds: the unit test should be as standalone as possible.  In the case I&#8217;m working on today, I have a client I&#8217;ve written (<a href="https://github.com/ghidinelli/cfpayment">and released</a>) for a third party API from <a href="http://www.batchbook.com">Batchbook</a>.  But I need to have some Batchbook-to-My-App conversions of JSON to queries and so forth.  I don&#8217;t want to bake those into my publicly-released client so what I really want in my production app is some bean config that looks like:</p>
<pre><code>&lt;bean id="BatchbookService" class="model.external.batchbook.BatchbookService"&gt;
	&lt;constructor-arg name="BatchbookClient"&gt;
		&lt;bean class="model.external.batchbook.batchbook"&gt;
	        &lt;constructor-arg name="apikey"&gt;&lt;value&gt;password&lt;/value&gt;&lt;/constructor-arg&gt;
	        &lt;constructor-arg name="endpoint"&gt;&lt;value&gt;https://somehost.batchbook.com/api/v1&lt;/value&gt;&lt;/constructor-arg&gt;
			&lt;constructor-arg name="restconsumer"&gt;&lt;ref bean="restconsumer" /&gt;&lt;/constructor-arg&gt;
			&lt;constructor-arg name="JSONUtil"&gt;&lt;ref bean="JSONUtil" /&gt;&lt;/constructor-arg&gt;
		&lt;/bean&gt;
	&lt;/constructor-arg&gt;
&lt;/bean&gt;
&lt;bean id="RestConsumer" class="model.external.restconsumer" /&gt;
&lt;bean id="JSONUtil" class="model.utils.JSONUtil" /&gt;</code></pre>
<p>But if my BatchbookClient is a nested constructor-arg, how can I get it from the beanfactory to test it?  </p>
<h2>Alternative Coldspring Configurations</h2>
<p>Turns out there&#8217;s more than one way to load beans into Coldspring and passing a path to an XML configuration file is just one.  Looking over the <a href="http://www.coldspringframework.org/downloads/ColdSpring_Reference.pdf">1.0 reference guide</a>, I found two additional methods of interest:</p>
<ul>
<li>loadBeansFromXmlRaw(xmlStringConfig, true)</li>
<li>loadBeansFromXmlObj(parsedXmlConfig, true)</li>
</ul>
<p>Applying that to my current unit test, my setup method now looks like:</p>
<pre><code>&lt;cffunction name="setup"&gt;
	&lt;cfset var beanConfigs = "" /&gt;
	&lt;cfset var localMode = true /&gt;

	&lt;cfsavecontent variable="beanConfigs"&gt;
		&lt;beans&gt;
			&lt;bean id="BatchbookService" class="model.external.batchbook.BatchbookService"&gt;
				&lt;constructor-arg name="BatchbookClient"&gt;&lt;ref bean="BatchbookClient" /&gt;&lt;/constructor-arg&gt;
			&lt;/bean&gt;
			&lt;bean id="BatchbookClient" class="model.external.batchbook.batchbook"&gt;
		        &lt;constructor-arg name="apikey"&gt;&lt;value&gt;password&lt;/value&gt;&lt;/constructor-arg&gt;
		        &lt;constructor-arg name="endpoint"&gt;&lt;value&gt;https://somehost.batchbook.com/api/v1&lt;/value&gt;&lt;/constructor-arg&gt;
				&lt;constructor-arg name="restconsumer"&gt;&lt;ref bean="restconsumer" /&gt;&lt;/constructor-arg&gt;
				&lt;constructor-arg name="JSONUtil"&gt;&lt;ref bean="JSONUtil" /&gt;&lt;/constructor-arg&gt;
			&lt;/bean&gt;
			&lt;bean id="RestConsumer" class="model.external.restconsumer" /&gt;
			&lt;bean id="JSONUtil" class="model.utils.JSONUtil" /&gt;
		&lt;/beans&gt;
	&lt;/cfsavecontent&gt;

	&lt;cfscript&gt;
		variables.beanFactory = createObject("component", "coldspring.beans.DefaultXmlBeanFactory").init();
		variables.beanFactory.loadBeansFromXmlRaw(beanConfigs, true);

		variables.bb = variables.beanFactory.getBean("BatchbookClient");
		variables.svc = variables.beanFactory.getBean("BatchbookService");
	&lt;/cfscript&gt;

&lt;/cffunction&gt;</code></pre>
<p>Coldspring now instantiates much quicker and it only loads beans related to my unit test.  Plus, I have the flexibility to directly address the BatchbookClient and run it through its paces alongside the BatchbookService. </p>
<p>If you only have a few unit tests, these improvements won&#8217;t mean much but as your test suites grow (and you work towards continuous deployment&#8230;) you will find that speeding up testing is always a good thing and helps encourage you to do it.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ghidinelli.com/2013/03/27/faster-unit-tests-with-coldspring-and-mxunit/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>CFPAYMENT 1.0 Released!</title>
		<link>http://www.ghidinelli.com/2012/03/21/cfpayment-released</link>
		<comments>http://www.ghidinelli.com/2012/03/21/cfpayment-released#comments</comments>
		<pubDate>Thu, 22 Mar 2012 02:41:33 +0000</pubDate>
		<dc:creator>brian</dc:creator>
				<category><![CDATA[Business]]></category>
		<category><![CDATA[ColdFusion]]></category>
		<category><![CDATA[My Software]]></category>
		<category><![CDATA[Web/Internet]]></category>
		<category><![CDATA[cfpayment]]></category>

		<guid isPermaLink="false">http://www.ghidinelli.com/?p=1456</guid>
		<description><![CDATA[CFPAYMENT, a ColdFusion payment processing library, goes gold with a 1.0 release!]]></description>
			<content:encoded><![CDATA[<p>Payment processing is a lot of boilerplate code that isn&#8217;t much fun to write.  More importantly, when you make a mistake, it usually means a lost sale or, worse, a double charge you have to track down and refund.  </p>
<p>CFPAYMENT makes payment processing easy.  It&#8217;s a ColdFusion library modeled after Ruby&#8217;s ActiveMerchant that normalizes various payment gateways to give developers a single interface to process credit card and ACH transactions.  After years of production use and tens of millions of dollars processed, I&#8217;m proud to announce a 1.0 release is now available at <a href="http://cfpayment.riaforge.org/">cfpayment.riaforge.org</a>.</p>
<p>There&#8217;s a lot of hard work from many different folks that have made this a success.  If you&#8217;re interested in learning more, download the file and check out <em>docs/cfpayment.pdf</em> or hit up the Google group at <a href="http://groups.google.com/group/cfpayment">http://groups.google.com/group/cfpayment</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ghidinelli.com/2012/03/21/cfpayment-released/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Create contacts with the Batchbook API + ColdFusion</title>
		<link>http://www.ghidinelli.com/2010/08/30/create-contacts-batchbook-rest-api-coldfusion</link>
		<comments>http://www.ghidinelli.com/2010/08/30/create-contacts-batchbook-rest-api-coldfusion#comments</comments>
		<pubDate>Tue, 31 Aug 2010 00:47:50 +0000</pubDate>
		<dc:creator>brian</dc:creator>
				<category><![CDATA[Business]]></category>
		<category><![CDATA[ColdFusion]]></category>
		<category><![CDATA[My Software]]></category>
		<category><![CDATA[Web/Internet]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[CFHTTP]]></category>
		<category><![CDATA[cloud]]></category>
		<category><![CDATA[REST]]></category>

		<guid isPermaLink="false">http://www.ghidinelli.com/?p=1112</guid>
		<description><![CDATA[Using the Batchbook REST API to create contact records from ColdFusion]]></description>
			<content:encoded><![CDATA[<p><img src="http://www.batchblue.com/images/batchBookLogo.gif" width="286" height="49" class="alignright" />This is going to be mostly code because I don&#8217;t have time to really annotate much.  However, if you&#8217;re an Adobe ColdFusion developer and you also use (or are considering) the very good <a href="http://www.batchblue.com">Batchbook Social CRM</a> then you may also want to use their <a href="http://developer.batchblue.com/">REST API</a> in order to programmatically create or read your contact data.</p>
<p>While Batchbook does have the pretty cool <a href="http://batchblue.com/webforms.html">web forms</a> which can capture contact data from any ole web form, it doesn&#8217;t give you total flexibility with filling in customized data fields.  In our case, we wanted to create companies rather than individuals as part of a sales pipeline so we needed to have more control than the web forms currently allow.</p>
<h2>Start with a contact form</h2>
<p>Here&#8217;s the HTML form that we&#8217;re using &#8211; it still uses the original web form field names so all I&#8217;ve done here is changed the form ACTION to point at my CFM instead of Batchbook:</p>
<pre><code>&lt;form method="post" action="https://ourserver.com/form.cfm"&gt;
&lt;input type="hidden" name="location[address][country]" value="US" /&gt;
&lt;h2&gt;Sign-up Now!&lt;/h2&gt;
&lt;table class="form"&gt;
&lt;tr&gt;&lt;td&gt;&lt;label&gt;Company *&lt;/label&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;input type="text" name="company[name]" size="30" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;label&gt;Account Type *&lt;/label&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;select name="supertags[sales first contact][plan]"&gt;&lt;option value=""&gt;Select a plan - you can change later&lt;/option&gt;&lt;option value="Plan 1"&gt;Plan Uno&lt;/option&gt;&lt;option value="Plan 2"&gt;Plan Dos&lt;/option&gt;&lt;option value="Plan 3"&gt;Plan Tres&lt;/option&gt;&lt;/select&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;label&gt;First Name *&lt;/label&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;input type="text" name="contact_details[first_name]" size="30" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;label&gt;Last Name *&lt;/label&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;input type="text" name="contact_details[last_name]" size="30" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;label&gt;Email *&lt;/label&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;input type="text" name="location[email]" size="30" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;label&gt;Phone *&lt;/label&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;input type="text" name="location[phone]" size="30" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;label&gt;Organization Address&lt;/label&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;input type="text" name="location[address][address_1]" size="30" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;label&gt;Address 2&lt;/label&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;input type="text" name="location[address][address_2]" size="30" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;label&gt;City, State Zip&lt;/label&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;input type="text" name="location[address][city]" size="15" /&gt;, &lt;input type="text" name="location[address][state]" size="4" /&gt; &lt;input type="text" name="location[address][zip_code]" size="10" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;label&gt;Company URL&lt;/label&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;input type="text" name="location[website]" size="40" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;label&gt;Do you have an existing service?  If so, which:&lt;/label&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;input type="text" name="supertags[sales first contact][existing_service]" size="40" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;label&gt;Date of next event:&lt;/label&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;input type="text" name="supertags[sales first contact][date_of_first_event]" size="15" /&gt;&lt;br /&gt;&lt;small&gt;Format date like mm/dd/yyyy&lt;/small&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;label&gt;Questions/Comments&lt;/label&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;textarea name="supertags[sales first contact][customer_comments]" rows="10" cols="50"&gt;&lt;/textarea&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr class="submit"&gt;&lt;td&gt;&lt;input class="button" type="submit" value="Request Account" /&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
&lt;/form&gt;</code></pre>
<p>It&#8217;s the same contact form you&#8217;ve whipped up 100 times before.  If you&#8217;re using the built in web forms, the field names must match what is above.  If I was writing my form handler from scratch I would have selected more normalized names.  </p>
<p>Note that we a hidden field at the beginning &#8211; not all of the data must be user editable.  Of course, I could also just set it in my form handler.  The country value is a holdover from the original web form.</p>
<h2>Process those fields</h2>
<p>Our next step is to process that form submission with ColdFusion and use the Batchbook API to create my contacts and populate my custom data fields:</p>
<pre><code>&lt; !--- credentials ---&gt;
&lt;cfset variables.api_key = 'YOUR-SECURITY-KEY' /&gt;&lt; !--- get this from "Your Account", right column ---&gt;
&lt;cfset variables.root_uri = 'https://[YOUR HOST].batchbook.com/service' /&gt;

&lt;cfhttp url="#variables.root_uri#/companies.xml" method="post" username="#variables.api_key#" password="x" charset="UTF-8" timeout="30" throwonerror="no"&gt;
	&lt;cfhttpparam name="company[name]" value="#form['company[name]']#" type="formfield" /&gt;
	&lt;cfhttpparam name="company[notes]" value="" type="formfield" /&gt;
&lt;/cfhttp&gt;
&lt; !--- cfdump var="#cfhttp#" ---&gt;

&lt;cfif structKeyExists(cfhttp, "responseheader") AND isStruct(cfhttp.responseheader) AND structKeyExists(cfhttp.responseheader, "location")&gt;

	&lt;cfhttp url="#cfhttp.responseheader.location#" method="get" username="#variables.api_key#" password="x" charset="UTF-8" timeout="30" throwonerror="no"&gt;
	&lt;/cfhttp&gt;
	&lt; !--- cfdump var="#cfhttp#" ---&gt;

	&lt;cfset xmlCompany = xmlParse(cfhttp.fileContent) /&gt;
	&lt;cfset id = xmlCompany.company.id.xmlText /&gt;

	&lt; !--- now add location to company ---&gt;
	&lt;cfhttp url="#variables.root_uri#/companies/#id#/locations.xml" method="post" username="#variables.api_key#" password="x" charset="UTF-8" timeout="30" throwonerror="no"&gt;
		&lt;cfhttpparam name="location[label]" value="work" type="formfield" /&gt;
		&lt;cfhttpparam name="location[email]" value="#form['location[email]']#" type="formfield" /&gt;
		&lt;cfhttpparam name="location[website]" value="#form['location[website]']#" type="formfield" /&gt;
		&lt;cfhttpparam name="location[phone]" value="#form['location[phone]']#" type="formfield" /&gt;
		&lt;cfhttpparam name="location[street_1]" value="#form['location[address][address_1]']#" type="formfield" /&gt;
		&lt;cfhttpparam name="location[street_2]" value="#form['location[address][address_2]']#" type="formfield" /&gt;
		&lt;cfhttpparam name="location[city]" value="#form['location[address][city]']#" type="formfield" /&gt;
		&lt;cfhttpparam name="location[state]" value="#form['location[address][state]']#" type="formfield" /&gt;
		&lt;cfhttpparam name="location[postal_code]" value="#form['location[address][zip_code]']#" type="formfield" /&gt;
		&lt;cfhttpparam name="location[country]" value="#form['location[address][country]']#" type="formfield" /&gt;
	&lt;/cfhttp&gt;
	&lt; !--- cfdump var="#cfhttp#" ---&gt;

	&lt; !--- set default values for sales super tag and capture user-provided information ---&gt;
	&lt;cfset arrSuperTag = arrayNew(1) /&gt;
	&lt;cfset arrayAppend(arrSuperTag, "super_tag[existing_service]=#URLEncodedFormat(form['supertags[sales first contact][existing_service]'])#") /&gt;
	&lt;cfset arrayAppend(arrSuperTag, "super_tag[date_of_first_event]=#URLEncodedFormat(form['supertags[sales first contact][date_of_first_event]'])#") /&gt;
	&lt;cfset arrayAppend(arrSuperTag, "super_tag[customer_comments]=#URLEncodedFormat(form['supertags[sales first contact][customer_comments]'])#") /&gt;
	&lt;cfset arrayAppend(arrSuperTag, "super_tag[plan]=#URLEncodedFormat(form['supertags[sales first contact][plan]'])#") /&gt;
	&lt;cfset arrayAppend(arrSuperTag, "super_tag[requested_demo]=#URLEncodedFormat(form['supertags[sales first contact][requested_demo]'])#") /&gt;
	&lt;cfset arrayAppend(arrSuperTag, "super_tag[active]=true") /&gt;
	&lt;cfset arrayAppend(arrSuperTag, "super_tag[paperwork_sent]=false") /&gt;
	&lt;cfset arrayAppend(arrSuperTag, "super_tag[agreement_back]=false") /&gt;
	&lt;cfset arrayAppend(arrSuperTag, "super_tag[announced_on_facebook]=false") /&gt;
	&lt;cfset arrayAppend(arrSuperTag, "super_tag[buddy_check_complete]=false") /&gt;	

	&lt;cfhttp url="#variables.root_uri#/companies/#id#/super_tags/sales.xml" method="put" username="#variables.api_key#" password="x" charset="UTF-8" timeout="30" throwonerror="no"&gt;
		&lt;cfhttpparam type="header" name="Content-Type" value="application/x-www-form-urlencoded" /&gt;
		&lt;cfhttpparam type="body" value="#arrayToList(arrSuperTag, "&amp;")#" /&gt;
	&lt;/cfhttp&gt;
	&lt; !--- cfdump var="#cfhttp#" ---&gt;

	&lt; !--- create person---&gt;
	&lt;cfhttp url="#variables.root_uri#/people.xml" method="post" username="#variables.api_key#" password="x" charset="UTF-8" timeout="30" throwonerror="no"&gt;
		&lt;cfhttpparam name="person[first_name]" value="#form['contact_details[first_name]']#" type="formfield" /&gt;
		&lt;cfhttpparam name="person[last_name]" value="#form['contact_details[last_name]']#" type="formfield" /&gt;
		&lt;cfhttpparam name="person[company]" value="#form['company[name]']#" type="formfield" /&gt;
	&lt;/cfhttp&gt;
	&lt; !--- cfdump var="#cfhttp#" ---&gt;
&lt;/cfif&gt;</code></pre>
<p>Some notes about the above:</p>
<ul>
<li><strong>What&#8217;s my password?</strong>  Your password is X.  Or Y.  Or Z.  It doesn&#8217;t matter &#8211; Batchbook only authenticates you on your unique key which is sent as the username.  The password can be anything.</li>
<li>If you&#8217;re new to REST APIs, I suggest enabling the CFDUMPs as they will show you how data comes and goes.  Plus, you&#8217;ll see what the response headers look like (hint, they aren&#8217;t all <em>200 OK</em>s).</li>
<li><strong>Associating people with companies</strong> &#8211; there is no official way to do this.  Just make sure the &#8220;company&#8221; value for the person is a string match for the company record and Batchbook will make the magic happen on their end.</li>
<li>There is basically no error checking or exception handling here &#8211; I&#8217;ve got my code wrapped up in some try/catch and I fall back to sending an email to us if all else fails.  <strong>Plan for failure.</strong> At some point the API will be down or the Internet will break and you need to have a contingency plan when dealing with remote third parties.</li>
</ul>
<p>The above code took me a day or two of toying around to get working properly.  While the docs are pretty good, my experience is that API implementations never quite match their documentation.  Getting super tags to work (key to our sales process) took a lot of fooling around plus some assistance from the very helpful Eric Krause at Batchbook.</p>
<p>Next post will be how we&#8217;re integrating the results of this sales pipeline with <a href="http://www.emailcenterpro.com">Email Center Pro</a> to dynamically generate and send out contracts in preparation for our sales cycle this winter.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ghidinelli.com/2010/08/30/create-contacts-batchbook-rest-api-coldfusion/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Bounce Detector r18 Released</title>
		<link>http://www.ghidinelli.com/2010/08/05/bounce-detector-r18-released</link>
		<comments>http://www.ghidinelli.com/2010/08/05/bounce-detector-r18-released#comments</comments>
		<pubDate>Fri, 06 Aug 2010 04:56:43 +0000</pubDate>
		<dc:creator>brian</dc:creator>
				<category><![CDATA[ColdFusion]]></category>
		<category><![CDATA[My Software]]></category>
		<category><![CDATA[Web/Internet]]></category>
		<category><![CDATA[bouncedetector]]></category>
		<category><![CDATA[opensource]]></category>

		<guid isPermaLink="false">http://www.ghidinelli.com/?p=1079</guid>
		<description><![CDATA[New release of Bounce Detector - returned email detection software for ColdFusion]]></description>
			<content:encoded><![CDATA[<p><img src="https://www.ghidinelli.com/wp-content/uploads/2010/08/2657896516_df3d23939c_m.jpg" alt="Got Email Bounces?  Kill it with Bounce Detector.  Image courtesy of SomewhatFrank: http://www.flickr.com/photos/somewhatfrank/" title="Got Email Bounces?  Kill it with Bounce Detector.  Image courtesy of SomewhatFrank: http://www.flickr.com/photos/somewhatfrank/" width="135" height="195" class="alignright" />Catching up on our unmatched nightly bounces, I released an updated version of my <a href="http://bouncedetector.riaforge.org">Bounce Detector</a> CFC on riaforge tonight.  Bounce Detector looks at returned email bodies and uses a lengthy signature list to identify the cause of the bounce.  Switching to an SVN commit release numbering system, tonight&#8217;s r18 includes a few important updates:</p>
<ol>
<li>New limited support for autoresponders and OOO messages which return a code &#8220;5&#8243;</li>
<li>Added new temporary, permanent and spam bounce signatures</li>
<li>Added new demo that doesn&#8217;t require Coldspring to simplify integration for new users</li>
</ol>
<p>Download it from <a href="http://bouncedetector.riaforge.org">bouncedetector.riaforge.org</a> today and upgrade!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ghidinelli.com/2010/08/05/bounce-detector-r18-released/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Using BounceDetector without Coldspring</title>
		<link>http://www.ghidinelli.com/2009/09/25/bouncedetector-without-coldspring</link>
		<comments>http://www.ghidinelli.com/2009/09/25/bouncedetector-without-coldspring#comments</comments>
		<pubDate>Fri, 25 Sep 2009 17:38:46 +0000</pubDate>
		<dc:creator>brian</dc:creator>
				<category><![CDATA[ColdFusion]]></category>
		<category><![CDATA[My Software]]></category>
		<category><![CDATA[Web/Internet]]></category>
		<category><![CDATA[bounce detector]]></category>
		<category><![CDATA[open source]]></category>

		<guid isPermaLink="false">http://www.ghidinelli.com/?p=964</guid>
		<description><![CDATA[How to use my ColdFusion BounceDetector without Coldspring to parse bounced email for temporary, permanent, spam and challenge-response failures.]]></description>
			<content:encoded><![CDATA[<p>James Moberg asked <a href="https://www.ghidinelli.com/2009/07/10/bouncedetector-1-1-released#comments">how to use BounceDetector without Coldspring</a> this morning.  Personally, I think you&#8217;re crazy if you don&#8217;t use Coldspring, but I whipped up a demo in order to explain how to parse the bounce signatures database and manually create the BounceDetector instance if desired.  You can find the file in subversion called demowithoutcoldspring.cfm.  The only real work is parsing the XML and replicating the functionality in Coldspring that creates an array of structs from an argument that uses the &lt;map&gt; and &lt;list&gt; tags:</p>
<pre><code>&lt;cffile action="read" file="#expandPath('bouncedetector.xml')#" variable="xmlBD" /&gt;
&lt;cfset xml = xmlParse(xmlBD) /&gt;
&lt;cfset arr = xml.beans.bean[1]["constructor-arg"].list.xmlChildren /&gt;

&lt;cfset sigs = arrayNew(1) /&gt;
&lt;cfloop from="1" to="#arrayLen(arr)#" index="ii"&gt;
	&lt;cfset n = {signature = arr[ii].xmlChildren[1].xmlChildren[1].xmlText
			,weight = arr[ii].xmlChildren[2].xmlChildren[1].xmlText } /&gt;
	&lt;cfset arrayAppend(sigs, n) /&gt;
&lt;/cfloop&gt;

&lt;cfset bd = createObject("component", "bouncedetector") /&gt;
&lt;cfset bd.init(mailBounceSignatures = sigs) /&gt;</code></pre>
<p>But seriously, just drop in Coldspring! </p>
]]></content:encoded>
			<wfw:commentRss>http://www.ghidinelli.com/2009/09/25/bouncedetector-without-coldspring/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Deploying assets to Amazon S3 with Ant</title>
		<link>http://www.ghidinelli.com/2009/09/02/deploying-assets-amazon-s3-ant</link>
		<comments>http://www.ghidinelli.com/2009/09/02/deploying-assets-amazon-s3-ant#comments</comments>
		<pubDate>Wed, 02 Sep 2009 17:19:18 +0000</pubDate>
		<dc:creator>brian</dc:creator>
				<category><![CDATA[My Software]]></category>
		<category><![CDATA[Research/HOWTO]]></category>
		<category><![CDATA[Web/Internet]]></category>
		<category><![CDATA[amazon]]></category>
		<category><![CDATA[Ant]]></category>
		<category><![CDATA[cloud]]></category>
		<category><![CDATA[deployment]]></category>
		<category><![CDATA[S3]]></category>
		<category><![CDATA[subversion]]></category>

		<guid isPermaLink="false">http://www.ghidinelli.com/?p=774</guid>
		<description><![CDATA[Ant script for deploying static web assets from subversion to Amazon S3 with minification, compression and concatenation for maximum performance.]]></description>
			<content:encoded><![CDATA[<p>In my previous post on <a href="https://www.ghidinelli.com/2009/07/19/storing-web-assets-amazon-s3">storing web assets on Amazon S3</a>, I promised to share the Ant script I developed from a week of work and testing to jumpstart your own efforts.  Let&#8217;s look at what I wanted to accomplish.</p>
<h2>The Ideal Deployment</h2>
<p>The ideal deployment would accomplish a number of things that I listed in my post such as:</p>
<ul>
<li>Combine multiple Javascripts or CSS files to reduce download count</li>
<li>Automatically set far-future headers, mime type, encoding and so forth</li>
<li>Grab remote scripts like Google Analytics or AddThis&#8217; sharing widget and bundle it into our core code  (see <a href="http://www.askapache.com/javascript/google-analytics-speed-tips.html">why</a> and <a href="http://www.google.com/support/googleanalytics/bin/answer.py?hl=en&#038;answer=55466">discussion</a>)</li>
<li>Strip and minify Javascript and CSS to reduce file size</li>
<li>Finally, compress text files for browsers that support Gzip and upload them automatically</li>
</ul>
<p>That&#8217;s a good wish list because it&#8217;s everything I did by hand the first time and it was a pain in the ass.  Being fully automated means with one simple execution and a couple of parameters, we can push an entire repository of web assets off to Amazon S3 and keep them up to date.</p>
<h2>Implementation</h2>
<p>Here&#8217;s the background on my site and why this script does what it does.  It&#8217;s easy to customize so you should be able to use it for your own needs with little effort but this gives some rationale as to why I&#8217;ve made certain decisions.</p>
<p>Be sure to check my previous post to fully understand the <a href="https://www.ghidinelli.com/2009/07/19/storing-web-assets-amazon-s3">limitations of S3</a>.  I won&#8217;t be re-addressing here why we have two buckets, set headers or compress content separately.</p>
<h3>Javascript</h3>
<p>On my site, we include <a href="http://www.google.com/analytics">Google Analytics</a> on every page.  We also include <a href="http://jquery.com">jQuery</a> and Dan Switzer&#8217;s <a href="http://www.pengoworks.com/qforms">qForms</a> on many pages.  The public facing part of our site also includes the <a href="http://www.addthis.com">AddThis</a> widget that lets people share our content via social media sites like Facebook and Twitter. </p>
<p>Depending on the page you hit, you might have to download all four of those things.  Or in certain rare cases, maybe just one of them (GA).   We made the decision to create a bundle of core Javascript that would include all four of those items in a single file.  When stripped and minified with a far futures expiry date (which means they would only ever download it once), we decided that the file size was small enough to send to every user instead of sending it piecemeal.  jQuery accounts for the biggest chunk and since we&#8217;re moving towards more interactivity, we decided to bite the bullet and bundle the four scripts together.</p>
<p>Javascript is compressed by the impressive <a href="http://www.julienlecomte.net/blog/2007/09/16/">YUI-Compressor</a> written by Julien Lecomte.</p>
<h3>CSS</h3>
<p>Historically, we&#8217;ve always used separate files for print and screen CSS.  However, there is a technique that will allow you to put both styles into the same spreadsheet that will not only eliminate the extra file but will also reduce the total amount of CSS needed.  It boils down to using <a href="http://www.thewebsqueeze.com/web-design-articles/yslow-going-from-f-to-a.html">this technique</a> in your single CSS file:</p>
<pre><code>@media screen {
    /* Rest of the screen stylesheet goes here */
}
@media print {
    /* Rest of the print stylesheet goes here */
}</code></pre>
<p>This first part will eliminate the second file but it doesn&#8217;t reduce the overall amount of CSS.  For that, I turned to this explanation of <a href="http://www.usabledevelopment.com/cssmedia">putting generic styles outside of the { }</a> which gives you a set of &#8220;base&#8221; styles that are then overridden by media-specific styles (in my case, print).  This is the &#8220;cascading&#8221; in Cascading Style Sheets.  You have to laugh when you work with something for years and still learn something so fundamental.  In the end, I have three CSS files that look like:</p>
<pre><code>pap_screen.css:
/* general CSS */

forums.css:
/* general forums css */

pap_print.css:
@media print {
  /* print specific overrides to the general CSS */
}</code></pre>
<p>During deployment, the script concatenates the three files together into a single CSS which handles both screen and print.  Then it&#8217;s minimized by YUI-Compressor and gzipped for a teensy end product.</p>
<p>Why keep the three separate files?  I find it easier to go into a smaller file to find a style than deal with one giant file all the time.  If I were starting from scratch, I probably wouldn&#8217;t have gone this way but since I already had the three files in my source control, I left them as-is and let Ant put them together.</p>
<h2>Updating files with far future Expires</h2>
<p>Astute readers will be wondering: if you set the cache headers to expire a year from now, how do you make changes to those files?  Won&#8217;t the browser use its local copy effectively ignoring the updated file on the server?</p>
<p>Yes.</p>
<p>When I consulted at Yahoo in 2003 helping them with a major search redesign, I was exposed to their internal interface to Akamai&#8217;s content distribution network.  Their answer to this problem was very simple: rename the file.  If the file was foo.css, name it foo2.css and update your HTML to point at it instead.  Assuming the actual HTML doesn&#8217;t also have a far futures expiry, then the next request will load foo2.css instead and see the updated styles.</p>
<p><em>This sounds like a pain in the butt but is not that bad.</em>  You should already have your templates abstracted in some fashion, either in a MVC system, custom tag or some other templating mechanism that separates out the core aspects of your look and feel.  That means when your core JS and CSS files are updated, and thus renamed, there should only be one or two places you need to make changes.</p>
<p>Ask yourself: is a tiny extra bit of work on your part worth a huge speed increase for every one of your users on every request ever made to your website?  There is only one correct answer to that question.</p>
<h2>Preparing Ant</h2>
<p>Let&#8217;s get to business.   In addition to Ant 1.7, you&#8217;ll need to also obtain the following libraries and Ant tasks:</p>
<ul>
<li><a href="http://subclipse.tigris.org/svnant.html">SVNAnt</a> &#8211; access Subversion repositories from Ant</li>
<li>YUI-Compressor &#8211; <a href="http://developer.yahoo.com/yui/compressor/">library</a> and <a href="http://code.google.com/p/javaflight-code/">Ant task</a> for compressing CSS and JS</li>
<li><a href="http://ant-contrib.sourceforge.net/">Ant-Contrib</a> &#8211; some extra Ant tasks for looping</li>
<li><a href="http://s3tools.org/s3cmd">s3cmd</a> &#8211; A python script for uploading/downloading files to S3, so you&#8217;ll need Python too)</li>
</ul>
<p>It&#8217;s beyond the scope of this document on how to get those working; you&#8217;ll need some Ant skills but basically it involves downloading the JARs and putting them into your Java lib/ext or ant/lib folder.  On CentOS Linux, that would be /usr/java/latest/jre/lib/ext and /usr/share/ant/lib.</p>
<h3>Why not jets3t?</h3>
<p>There is a pure Java interface for S3, <a href="https://jets3t.dev.java.net/">jets3t</a>, but it didn&#8217;t work for my purposes here.  It may change over time and would be preferable to an external dependency like s3cmd.</p>
<h3>Properties Files</h3>
<p>I use one properties file per environment that I deploy to and my Ant scripts ask me which environment I want to target when they start.  For the script below, they would be named production.properties, staging.properties and development.properties:</p>
<pre><code># for production cdn
project.name=cdn
project.defaultTarget=deploy
svn.project=cdn
root.buildpath=/tmp
root.deploypath=/my/web/folder
jar.path=/usr/share/ant/lib

# aws props (we have two hosts, one is static, one is compressed, so two buckets)
aws.bucket.uncompressed=cdn-sitename
aws.bucket.compressed=cdnz-sitename
aws.accessId=yourAccessId
aws.secretKey=yourSecretKey

# system settings
exec.python=/usr/bin/python
exec.s3cmd=/usr/bin/s3cmd

# concatenated properties and subversion details
project.build.root=${root.buildpath}/${project.name}/build
project.clean.root=${root.buildpath}/${project.name}/clean
project.compile.root=${root.buildpath}/${project.name}/compile
project.compressed.root=${root.buildpath}/${project.name}/clean-compressed
project.deploy.root=${root.deploypath}/${project.name}

# construct an SVN path like http://server/root/project/ for checkout/update
svn.rooturl=http://your.subversion.com/server/root
svn.projecturl=${svn.rooturl}${svn.project}/trunk/</code></pre>
<p>For running on Windows, I change a few of the key parameters like so:</p>
<pre><code>root.buildpath=c:/temp
root.deploypath=c:/Documents and Settings/brian/My Documents/web
jar.path=c:\\apache-ant-1.7.1\\lib

exec.python=c:\\progra~1\\python\\python.exe
exec.s3cmd=c:\\progra~1\\python\\Scripts\\s3cmd.py</code></pre>
<h2>The Script</h2>
<p>With all of the preparations done, we can try the actual Ant script.   I&#8217;m sorry the formatting of this makes you scroll horizontally; I&#8217;m going to get a new code plugin soon to eliminate this.  You might <a href="https://www.ghidinelli.com/wp-content/uploads/2009/09/build.xml">download the file</a> to follow along instead.</p>
<pre><code>&lt;?xml version="1.0"?&gt;
&lt;project name="myCDN" default="picktarget" basedir="."&gt;

	<!-- report props -->
	&lt;property name="email.to" value="email@domain.com" /&gt;
	&lt;property name="email.from" value="email@domain.com" /&gt;

	<!-- init the DSTAMP, TSTAMP, and TODAY properties -->
	&lt;tstamp&gt;
		&lt;format property="svn.builddate" pattern="yyMMddhhmm"/&gt;
	&lt;/tstamp&gt;

	<!-- Define log file -->
	&lt;record name="build.logfile" /&gt;

	<!-- pick up env.COMPUTERNAME -->
	&lt;property environment="env"/&gt;</code></pre>
<p>The first and default target is picktarget &#8211; this prompts the user for which environment to deploy to and sets some Ant properties for helper libraries and options before jumping off to the target specified in the properties file:</p>
<pre><code>	<!-- TARGETS -->
	&lt;target name="picktarget"&gt;
		&lt;input message="Do you want to deploy?" validargs="production,staging,development" addproperty="target" /&gt;

		&lt;echo&gt;Deploy target: ${target}&lt;/echo&gt;

		<!-- if the file exists, target.props.available will exist, otherwise it won't! -->
		&lt;available file="${target}.properties" property="target.props.available" /&gt;

		<!-- if target.props.available DOESNT exist, we fail with a message -->
		&lt;fail message="The properties file, ${target}.properties, could not be found!" unless="target.props.available" /&gt;

		<!-- read in props including AWS credentials -->
		&lt;property file="${target}.properties" /&gt;

		&lt;taskdef name="svn" classname="org.tigris.subversion.svnant.SvnTask"&gt;
			&lt;classpath&gt;
				&lt;fileset dir="${jar.path}"&gt;
					&lt;include name="**/svn*.jar"/&gt;
				&lt;/fileset&gt;
			&lt;/classpath&gt;
		&lt;/taskdef&gt;		

		<!-- yui-compressor task definition -->
		&lt;path id="yui.classpath"&gt;
			&lt;pathelement location="${jar.path}/yuicompressor-2.4.2.jar" /&gt;
			&lt;pathelement location="${jar.path}/yui-compressor-ant-task-0.4.jar" /&gt;
		&lt;/path&gt;		

		&lt;taskdef name="yui-compressor" classname="net.noha.tools.ant.yuicompressor.tasks.YuiCompressorTask"&gt;
			&lt;classpath refid="yui.classpath" /&gt;
		&lt;/taskdef&gt;

		<!-- bring antcontrib for/foreach support -->
		&lt;taskdef resource="net/sf/antcontrib/antlib.xml"&gt;
			&lt;classpath&gt;
				&lt;pathelement location="${jar.path}/ant-contrib-1.0b3.jar" /&gt;
			&lt;/classpath&gt;
		&lt;/taskdef&gt;

		<!-- redirect to desired target -->
		&lt;antcall target="${project.defaultTarget}" /&gt;

	&lt;/target&gt;	</code></pre>
<p>I like to have an init target that cleans up existing directories and prepares the script to run.  I used to remove the directory completely and recreate it but it extends the length of time that SVN exports or checkouts take over slow networks so I generally keep the source directory and only rebuild the work directory now:</p>
<pre><code>
	&lt;target name="init" description="Create temp local directories for build"&gt;

		<!-- does nothing if dir already exists -->
		&lt;mkdir dir="${project.build.root}" /&gt;

		<!-- leave deleted because svn export will create it -->
		&lt;delete dir="${project.clean.root}" /&gt;

		&lt;delete dir="${project.compile.root}" /&gt;
		&lt;mkdir dir="${project.compile.root}" /&gt;

		&lt;echo message="Temporary build directories created successfully!" /&gt;

		<!-- if we have a working copy, we'll just update to save time/bandwidth, /images is a directory that my static assets repo contains so I check for it  -->
		&lt;available file="${project.build.root}/images" property="target.exists" /&gt;

		<!-- either update or checkout the repo -->
		&lt;antcall target="-checkout" /&gt;
		&lt;antcall target="-update" /&gt;

		<!-- now export to the clean dir -->
		&lt;svn username="${svn.username}" password="${svn.password}" javahl="false"&gt;
			&lt;export srcUrl="${svn.projecturl}" destPath="${project.clean.root}" /&gt;
		&lt;/svn&gt;

	&lt;/target&gt;</code></pre>
<p>The hyphen in front of this target name indicates it is private.  I only call this as a dependency from other targets using the &#8220;depends&#8221; syntax.  This is pretty straightforward &#8211; it uses the Svnant library to checkout my source code from Subversion using the properties file we specified.</p>
<p>I like to use the current svn revision number as my release number and I embed it in my application for reference.  I also print it to the screen while deploying.</p>
<pre><code>	&lt;target name="-checkout" description="Pulls code from Subversion into the build directory" unless="target.exists"&gt;

		<!-- get the repo info -->
		&lt;echo message="Checking out files from svn repository:" /&gt;
		&lt;input message="Please enter svn repo username:" addproperty="svn.username" /&gt;
		&lt;input message="Please enter svn repo password:" addproperty="svn.password" /&gt;
		&lt;input message="Enter version to deploy (default: HEAD):" addproperty="svn.revision" defaultvalue="HEAD" /&gt;

		<!-- checkout and get version -->
		&lt;svn username="${svn.username}" password="${svn.password}" javahl="false"&gt;
			&lt;checkout url="${svn.projecturl}" destPath="${project.build.root}" revision="${svn.revision}" recurse="true" /&gt;
			&lt;status path="${project.build.root}" revisionProperty="revision" /&gt;
		&lt;/svn&gt;

		&lt;echo&gt;Release version is ${revision}&lt;/echo&gt; 

	&lt;/target&gt;

	&lt;target name="-update" description="svn update a working copy instead" if="target.exists"&gt;

		&lt;echo message="Updating existing working copy:" /&gt;
		&lt;input message="Enter version to deploy (default: HEAD):" addproperty="svn.revision" defaultvalue="HEAD" /&gt;

		&lt;svn javahl="false"&gt;
			&lt;update dir="${project.build.root}" revision="${svn.revision}" recurse="true" /&gt;
			&lt;status path="${project.build.root}" revisionProperty="revision" /&gt;
		&lt;/svn&gt;

		&lt;echo&gt;Release version is ${revision}&lt;/echo&gt; 

	&lt;/target&gt;</code></pre>
<p>Now we start to get to the good stuff.  Here I&#8217;m creating the directory structure to build my static assets with a directory for concatenating and minimizing Javascript and CSS files.  These files wind up in a /global directory during deployment for inclusion in my HTML templates.</p>
<pre><code>
	&lt;target name="precompile" description="set up compilation environment"&gt;

		&lt;mkdir dir="${project.compile.root}" /&gt;
		&lt;mkdir dir="${project.compile.root}/js" /&gt;
		&lt;mkdir dir="${project.compile.root}/css" /&gt;

	&lt;/target&gt;

	&lt;target name="-precombine"&gt;

		<!-- must first clean up or else this won't regenerate new files -->
		&lt;delete&gt;
			&lt;fileset dir="${project.compile.root}/js" includes="*.js" /&gt;
			&lt;fileset dir="${project.compile.root}/css" includes="*.css" /&gt;
		&lt;/delete&gt;
		&lt;delete dir="${project.clean.root}/global" /&gt;

	&lt;/target&gt;</code></pre>
<p>One of the challenges of bundling Google Analytics and AddThis javascript code into your app is that you&#8217;re no longer getting their updates on every page request.  Generally this is OK &#8211; you probably don&#8217;t need an update from them every day.  But from time to time, there are new features and enhancements (especially in GA) that you&#8217;ll want to capture and update in your bundle.  I&#8217;ve automated this process by fetching those files during deployment so I have the latest each time I deploy.</p>
<p><em>Note:</em> because of the way the combined files are named, you may need to update your templates when you deploy your assets!  </p>
<pre><code>	&lt;target name="fetch" description="Retrieve external resources for inclusion"&gt;

		<!-- get remote files -->
		&lt;get src="http://www.google-analytics.com/ga.js" dest="${project.compile.root}/js/ga.js" verbose="true" /&gt;
		&lt;get src="https://secure.addthis.com/js/200/addthis_widget.js" dest="${project.compile.root}/js/sharethis.js" verbose="true" /&gt;

	&lt;/target&gt;</code></pre>
<p>Now take all of the Javascript and CSS files and concatenate them into fewer files.  Note that ORDER MATTERS!  We list the files in an explicit order to satisfy any dependencies that we may have in the system.  Once combined, we run them through YUI-compressor to squeeze them down and finally rename them back to their original names.</p>
<pre><code>
	&lt;target name="combine" description="Combine permitted files together for deployment" depends="-precombine,fetch"&gt;

		&lt;echo message="Building global javascript and style sheets..." /&gt;

		&lt;concat destfile="${project.compile.root}/js/core.js" encoding="UTF8" eol="unix" force="no"&gt;
			<!-- explicitly order concat because ordering matters here -->
			&lt;fileset dir="${project.clean.root}" includes="js/library/jquery/1.3.2/jquery.js" /&gt;
			&lt;fileset dir="${project.clean.root}" includes="js/qforms/qforms-combined.js" /&gt;
			&lt;fileset dir="${project.compile.root}" includes="js/ga.js" /&gt;
			&lt;fileset dir="${project.compile.root}" includes="js/sharethis.js" /&gt;
		&lt;/concat&gt;

		&lt;concat destfile="${project.compile.root}/js/forms.js" encoding="UTF8" eol="unix" force="no"&gt;
			<!-- explicitly order concat because ordering matters here -->
			&lt;fileset dir="${project.clean.root}" includes="js/library/jquery/1.2.3/jquery.color.js" /&gt;
			&lt;fileset dir="${project.clean.root}" includes="js/library/jquery/1.2.6/jquery.autocomplete.js" /&gt;
			&lt;fileset dir="${project.clean.root}" includes="js/library/jquery/1.3.1/jquery.checkboxes.min.js" /&gt;
			&lt;fileset dir="${project.clean.root}" includes="js/library/jquery/1.3.1/jquery.selectboxes.min.js" /&gt;
			&lt;fileset dir="${project.clean.root}" includes="js/library/jquery/1.3.1/jquery.field.min.js" /&gt;
			&lt;fileset dir="${project.clean.root}" includes="js/library/jquery/1.3.1/jquery.colorbox.min.js" /&gt;
		&lt;/concat&gt;

		&lt;concat destfile="${project.compile.root}/css/pap.css" encoding="UTF8" eol="unix" force="no"&gt;
			<!-- explicitly order concat because ordering matters here -->
			&lt;fileset dir="${project.clean.root}" includes="css/pap.css" /&gt;
			&lt;fileset dir="${project.clean.root}" includes="css/forums.css" /&gt;
			&lt;fileset dir="${project.clean.root}" includes="css/pap_print.css" /&gt;
		&lt;/concat&gt;

		&lt;concat destfile="${project.compile.root}/css/pmp.css" encoding="UTF8" eol="unix" force="no"&gt;
			<!-- explicitly order concat because ordering matters here -->
			&lt;fileset dir="${project.clean.root}" includes="css/pmp.css" /&gt;
			&lt;fileset dir="${project.clean.root}" includes="css/forums.css" /&gt;
			&lt;fileset dir="${project.clean.root}" includes="css/pmp_print.css" /&gt;
		&lt;/concat&gt;

		&lt;concat destfile="${project.compile.root}/css/regform.css" encoding="UTF8" eol="unix" force="no"&gt;
			<!-- explicitly order concat because ordering matters here -->
			&lt;fileset dir="${project.clean.root}" includes="css/jquery.colorbox.css" /&gt;
			&lt;fileset dir="${project.clean.root}" includes="css/regform.css" /&gt;
		&lt;/concat&gt;

		<!-- invoke compressor -->
		&lt;yui-compressor warn="false" munge="true" charset="UTF-8" fromdir="${project.compile.root}" todir="${project.compile.root}"&gt;
			&lt;include name="js/core.js" /&gt;<!-- creates version named js/core-min.js -->
			&lt;include name="js/forms.js" /&gt;
			&lt;include name="css/pap.css" /&gt;
			&lt;include name="css/pmp.css" /&gt;
			&lt;include name="css/regform.css" /&gt;
		&lt;/yui-compressor&gt;

		<!-- push these updated files back into the clean repo in anticipation of being pushed to s3 -->
		&lt;mkdir dir="${project.clean.root}/global" /&gt;

		<!-- special treatment of core -->
		&lt;copy file="${project.compile.root}/js/core-min.js" tofile="${project.clean.root}/global/core.js" /&gt;
		&lt;copy file="${project.compile.root}/js/forms-min.js" tofile="${project.clean.root}/global/forms.js" /&gt;
		&lt;copy file="${project.compile.root}/css/pap-min.css" tofile="${project.clean.root}/global/pap.css" /&gt;
		&lt;copy file="${project.compile.root}/css/pmp-min.css" tofile="${project.clean.root}/global/pmp.css" /&gt;
		&lt;copy file="${project.compile.root}/css/regform-min.css" tofile="${project.clean.root}/global/regform.css" /&gt;

	&lt;/target&gt;</code></pre>
<p>As we discussed above, when you set a far-futures expires header on a file and you need to change that file, the only realistic strategy is to rename it.  This next target does that automatically by using an md5 hash (Ant&#8217;s &#8220;checksum&#8221;) as part of the filename.  Why use this instead of say the revision number?  Because we only want to update the references to these files in our HTML templates if something actually changes.  It&#8217;s quite possible you could push your static assets many times and unless you updated your own Javascript or CSS or Google or AddThis updated theirs, you may not actually have any changes.  Thus, save yourself the effort of updating your HTML template references by leaving the file name the same.</p>
<p>I will admit that I don&#8217;t like using an md5 hash because it&#8217;s hard to spot check for changes.  I haven&#8217;t come up with anything better yet.</p>
<p>The end of this target prints the filenames to the output so you can compare them to the ones in your templates.  If you were clever, you would put these filenames into a config file of some sort that your templates used so you didn&#8217;t have to actually update the templates each time.</p>
<pre><code>	&lt;target name="versionize" description="Rename files based upon their checksums as a versioning system for the CDN"&gt;

		<!-- md5 hash in name means that anytime files are modified, the rolled up version name will change.
		     since you'll need to change this in your HTML, the client will be forced to download the updated version
		     which eliminates having to worry about long lived Expires headers -->
		&lt;checksum file="${project.clean.root}/global/core.js" property="chksum.core" /&gt;
		&lt;move file="${project.clean.root}/global/core.js" tofile="${project.clean.root}/global/core_${chksum.core}.js" /&gt;

		&lt;checksum file="${project.clean.root}/global/forms.js" property="chksum.forms" /&gt;
		&lt;move file="${project.clean.root}/global/forms.js" tofile="${project.clean.root}/global/forms_${chksum.forms}.js" /&gt;

		&lt;checksum file="${project.clean.root}/global/pap.css" property="chksum.pap" /&gt;
		&lt;move file="${project.clean.root}/global/pap.css" tofile="${project.clean.root}/global/pap_${chksum.pap}.css" /&gt;

		&lt;checksum file="${project.clean.root}/global/pmp.css" property="chksum.pmp" /&gt;
		&lt;move file="${project.clean.root}/global/pmp.css" tofile="${project.clean.root}/global/pmp_${chksum.pmp}.css" /&gt;

		&lt;checksum file="${project.clean.root}/global/regform.css" property="chksum.regform" /&gt;
		&lt;move file="${project.clean.root}/global/regform.css" tofile="${project.clean.root}/global/regform_${chksum.regform}.css" /&gt;

		&lt;echo&gt;Most recent globals, compare with previous:&lt;/echo&gt;
		&lt;for param="file"&gt;
			&lt;path&gt;
				&lt;fileset dir="${project.clean.root}/global/" /&gt;
			&lt;/path&gt;
			&lt;sequential&gt;
				&lt;echo message="@{file}" /&gt;
			&lt;/sequential&gt;
		&lt;/for&gt;

	&lt;/target&gt;</code></pre>
<p>When developing locally for example, I may wish to push to a local directory rather than to S3.  On our live servers, we keep a copy of our static assets on the boxes alongside our production code.  In case S3 were to have a serious outage, we could update one configuration file and suddenly start using our local assets again instead of S3.  Given that we don&#8217;t control S3, we feel this is a good backup strategy.</p>
<pre><code>	&lt;target name="localdeploy" description="Finalize build and stop before deploying to CDN"&gt;

		&lt;echo message="Exporting file transfer to ${project.deploy.root}..." /&gt;

		<!-- clean up permissions and ownership -->
		&lt;chmod dir="${project.clean.root}" type="file" perm="0644" /&gt;
		&lt;chmod dir="${project.clean.root}" type="dir" perm="0755" /&gt;

		<!-- copy to local destination dir -->
		&lt;mkdir dir="${project.deploy.root}" /&gt;
		&lt;sync todir="${project.deploy.root}" includeEmptyDirs="true" overwrite="true"&gt;
			&lt;fileset dir="${project.clean.root}" /&gt;
		&lt;/sync&gt;

	&lt;/target&gt;</code></pre>
<p>Now we use our Python synchronization utility and push our uncompressed (non-Gzipped) assets to S3.  This goes to the first of the two buckets we defined earlier.  </p>
<p>Technically the expires header should not be set further than a year in advance according to the RFP but we&#8217;re using 12/31/2010 because I&#8217;m lazy.</p>
<p>Notice how the S3 library is being instructed to add the HTTP headers like Cache-Control and Expires and mime-type.  Those are critical!</p>
<pre><code>	&lt;target name="push" description="take a finished build and move it to Amazon S3"&gt;

		<!-- hardcoded for now, until we see if changing the headers will force a re-upload -->
		&lt;property name="http.expires" value="Fri, 31 Dec 2010 12:00:00 GMT" /&gt;<!-- technically should not be > 1 year: http://code.google.com/speed/page-speed/docs/caching.html -->

		<!-- push all files with long expires/cache headers -->
		&lt;exec executable="${exec.python}" failonerror="true"&gt;
			&lt;arg value="${exec.s3cmd}" /&gt;
			&lt;arg value="--guess-mime-type" /&gt;
			&lt;arg value="--add-header=Cache-Control:public, max-age=630657344" /&gt;<!-- equivalent to 7229 days or so -->
			&lt;arg value="--add-header=Expires:${http.expires}" /&gt;
			&lt;arg value="--encoding=UTF-8" /&gt;
			&lt;arg value="--skip-existing" /&gt;<!-- we have to rename to version, so reuploading existing doesn't make sense -->
			&lt;arg value="--recursive" /&gt;
			&lt;arg value="--acl-public" /&gt;<!-- public read -->
			&lt;arg value="sync" /&gt;
			&lt;arg value="${project.clean.root}/" /&gt;
			&lt;arg value="s3://${aws.bucket.uncompressed}/" /&gt;
		&lt;/exec&gt;		

	&lt;/target&gt;</code></pre>
<p>We also want to push gzipped assets to S3 as well so this private target finds all text assets and compresses them:</p>
<pre><code>	&lt;target name="-compress" description="gzip text files for additional speed"&gt;

		&lt;delete dir="${project.compressed.root}" /&gt;
		&lt;mkdir dir="${project.compressed.root}" /&gt;

		<!-- make copy so we don't double-compress files -->
		&lt;sync todir="${project.compressed.root}" includeEmptyDirs="true" overwrite="true"&gt;
			&lt;fileset dir="${project.clean.root}" /&gt;
		&lt;/sync&gt;

		&lt;for param="file"&gt;
			&lt;path&gt;
				&lt;fileset dir="${project.compressed.root}" includes="**/*.js" /&gt;
				&lt;fileset dir="${project.compressed.root}" includes="**/*.css" /&gt;
				&lt;fileset dir="${project.compressed.root}" includes="**/*.xml" /&gt;<!-- fckeditor files -->
				&lt;fileset dir="${project.compressed.root}" includes="**/*.html" /&gt;<!-- fckeditor files -->
			&lt;/path&gt;
			&lt;sequential&gt;
				<!-- we keep the filenames the same and transparently serve gzipped content via http headers -->
				&lt;gzip src="@{file}" destfile="@{file}.gz" /&gt;
				&lt;move file="@{file}.gz" tofile="@{file}" overwrite="true" /&gt;
			&lt;/sequential&gt;
		&lt;/for&gt;

	&lt;/target&gt;</code></pre>
<p>And finally, repeat the push but this time to our compressed bucket.  We use two uploads here &#8211; one for the uncompressed content like images which won&#8217;t be Gzipped and another for the compressed content that includes a couple of additional headers for Vary and Content-Encoding so that browsers and proxies will know what to do with it:</p>
<pre><code>	&lt;target name="push-compressed" description="take a finished build and move it to Amazon S3" depends="-compress"&gt;

		<!-- hardcoded for now, until we see if changing the headers will force a re-upload -->
		&lt;property name="http.expires" value="Fri, 31 Dec 2010 12:00:00 GMT" /&gt;

		<!-- push all non-compressed files first, then just compressed second -->
		&lt;exec executable="${exec.python}" failonerror="true"&gt;
			&lt;arg value="${exec.s3cmd}" /&gt;
			&lt;arg value="--skip-existing" /&gt;<!-- we have to rename to version, so reuploading existing doesn't make sense -->
			&lt;arg value="--guess-mime-type" /&gt;
			&lt;arg value="--exclude=*.js" /&gt;<!-- these are the four types we compress, so exclude -->
			&lt;arg value="--exclude=*.css" /&gt;
			&lt;arg value="--exclude=*.xml" /&gt;
			&lt;arg value="--exclude=*.html" /&gt;
			&lt;arg value="--add-header=Cache-Control:public, max-age=630657344" /&gt;<!-- equivalent to 7229 days or so -->
			&lt;arg value="--add-header=Expires:${http.expires}" /&gt;
			&lt;arg value="--encoding=UTF-8" /&gt;
			&lt;arg value="--recursive" /&gt;
			&lt;arg value="--acl-public" /&gt;<!-- public read -->
			&lt;arg value="sync" /&gt;
			&lt;arg value="${project.compressed.root}/" /&gt;
			&lt;arg value="s3://${aws.bucket.compressed}/" /&gt;
		&lt;/exec&gt;

		&lt;exec executable="${exec.python}" failonerror="true"&gt;
			&lt;arg value="${exec.s3cmd}" /&gt;
			&lt;arg value="--skip-existing" /&gt;<!-- we have to rename to version, so reuploading existing doesn't make sense -->
			&lt;arg value="--guess-mime-type" /&gt;
			&lt;arg value="--exclude=*" /&gt;<!-- now ONLY upload those files which are compressed, with gzip header -->
			&lt;arg value="--include=*.js" /&gt;
			&lt;arg value="--include=*.css" /&gt;
			&lt;arg value="--include=*.xml" /&gt;
			&lt;arg value="--include=*.html" /&gt;
			&lt;arg value="--add-header=Cache-Control:public, max-age=630657344" /&gt;<!-- equivalent to 7229 days or so -->
			&lt;arg value="--add-header=Expires:${http.expires}" /&gt;
			&lt;arg value="--add-header=Vary:Accept-Encoding" /&gt;<!-- tell proxies to only serve this compressed content to a client who has the accept-encoding header: http://blog.port80software.com/2005/01/21/ -->
			&lt;arg value="--add-header=Content-Encoding:gzip" /&gt;
			&lt;arg value="--encoding=UTF-8" /&gt;
			&lt;arg value="--recursive" /&gt;
			&lt;arg value="--acl-public" /&gt;<!-- public read -->
			&lt;arg value="sync" /&gt;
			&lt;arg value="${project.compressed.root}/" /&gt;
			&lt;arg value="s3://${aws.bucket.compressed}/" /&gt;
		&lt;/exec&gt;		

	&lt;/target&gt;	</code></pre>
<p>I like to get an email when deployments are run, so we include this:</p>
<pre><code>	<!-- optional email -->
	&lt;target name="sendMail" description="Send email notification"&gt;
		<!-- clean up logfile to not have bare linefeeds (prohibited by qmail!) -->
		&lt;fixcrlf srcdir="." includes="**/*.logfile" eol="crlf" eof="remove" /&gt;

		<!-- send mail -->
		&lt;mail mailhost="yourmailserver.com" mailport="25"
				subject="'${target}' build at revision ${revision} successful"
				messagefile="build.logfile"
				encoding="plain"&gt;
		  &lt;from address="${email.to}"/&gt;
		  &lt;to address="${email.from}"/&gt;
		&lt;/mail&gt;
	    &lt;echo message="Mail sent!"/&gt;
	&lt;/target&gt;		</code></pre>
<p>And finally, the roll up targets.  None of the above targets are really designed to be called directly.  Rather, the following targets use the &#8220;depends&#8221; feature of Ant to combine multiple targets into something useful like: predeploy, deploy, redeploy, localpush, and repush.  Those should all be semi-guessable in terms of what they accomplish.</p>
<pre><code>	&lt;target name="deploy" depends="init,precompile,combine,versionize,localdeploy,push,push-compressed,sendMail"&gt;&lt;/target&gt;

	&lt;target name="predeploy" depends="init,precompile,combine,versionize,-compress"&gt;&lt;/target&gt;

	&lt;target name="redeploy" depends="combine,versionize,localdeploy"&gt;&lt;/target&gt;

	&lt;target name="localpush" depends="init,precompile,combine,versionize,localdeploy"&gt;&lt;/target&gt;

	&lt;target name="repush" depends="push,push-compressed"&gt;&lt;/target&gt;

&lt;/project&gt;</code></pre>
<p>You can <a href="https://www.ghidinelli.com/wp-content/uploads/2009/09/build.xml">download the full build.xml</a>.</p>
<p>I hope this Ant file helps you get up and running with S3.  If you have ideas or improvements, please leave them in the comments!</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ghidinelli.com/2009/09/02/deploying-assets-amazon-s3-ant/feed</wfw:commentRss>
		<slash:comments>12</slash:comments>
		</item>
		<item>
		<title>Flowplayer plugin for Codex wiki</title>
		<link>http://www.ghidinelli.com/2009/08/13/flowplayer-plugin-for-codex-wiki</link>
		<comments>http://www.ghidinelli.com/2009/08/13/flowplayer-plugin-for-codex-wiki#comments</comments>
		<pubDate>Fri, 14 Aug 2009 00:35:14 +0000</pubDate>
		<dc:creator>brian</dc:creator>
				<category><![CDATA[ColdFusion]]></category>
		<category><![CDATA[My Software]]></category>
		<category><![CDATA[Web/Internet]]></category>
		<category><![CDATA[jQuery]]></category>
		<category><![CDATA[codex]]></category>
		<category><![CDATA[coldbox]]></category>
		<category><![CDATA[flowplayer]]></category>
		<category><![CDATA[plugin]]></category>
		<category><![CDATA[wiki]]></category>

		<guid isPermaLink="false">http://www.ghidinelli.com/?p=899</guid>
		<description><![CDATA[Embed video in your Codex wiki with the excellent YouTube-like Flowplayer and this plugin!]]></description>
			<content:encoded><![CDATA[<p>I&#8217;m busy setting up a <a href="http://www.codexwiki.org">Codex Wiki</a> as a help system and needed the ability to embed screencast tutorials inline.  There is a flash embed plugin but I wanted something more Youtube-like that didn&#8217;t download the video unless the user clicks on it in order to save bandwidth and keep the page loading quickly as we plan to have <em>lots</em> of videos.</p>
<p>Cue <a href="http://www.flowplayer.org">FlowPlayer</a> and a short plugin I wrote.  FlowPlayer is pretty impressive&#8230; it&#8217;s a SWF and a JS file you drop into your site and it leverages the jQuery built into Codex.  If you want to use it in conjunction with Codex, you can use my plugin below.  The wiki syntax looks like:</p>
<pre><code>{{{ev url="http://url/to/your/movie.flv"
         height="300"
         width="400"
         alt="My alternate text description"
         splash="http://url/to/a/static/splash/screen.jpg"}}}</code></pre>
<p>That code will give you a player that looks roughly like:</p>
<p><img src="https://www.ghidinelli.com/wp-content/uploads/2009/08/evembed.png" alt="Embed Video plugin for Codex Wiki example" title="Embed Video plugin for Codex Wiki example" width="421" height="321" class="aligncenter" /></p>
<p>This was my first plugin for a <a href="http://www.coldboxframework.org">Coldbox</a> application and it was pretty easy to take an existing one and create another.  It&#8217;s also the first Coldbox application I&#8217;ve worked with and so far I&#8217;m pretty impressed.  I am looking at giving it a test drive for a mini app I have to build later this month for club elections and surveys.</p>
<p>Just drop this into your codex/plugins/wiki directory as ev.cfc.  The way I&#8217;ve written the code, you can have multiple players per page as they&#8217;ll all be tagged with a class &#8220;flowplayer&#8221;:</p>
<pre><code>&lt; !-----------------------------------------------------------------------
********************************************************************************
Copyright 2009 by Brian Ghidinelli (https://www.ghidinelli.com)
********************************************************************************
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
********************************************************************************
$Build Date: @@build_date@@
$Build ID:	@@build_id@@
********************************************************************************
-----------------------------------------------------------------------&gt;
&lt;cfcomponent name="EmbedVideo"
			 hint="A wiki plugin to embed video using FlowPlayer"
			 extends="codex.model.plugins.BaseWikiPlugin"
			 output="false"
			 cache="false"&gt;

&lt; !------------------------------------------- CONSTRUCTOR -------------------------------------------&gt;	

    &lt;cffunction name="init" access="public" returntype="ev" output="false"&gt;
		&lt;cfargument name="controller" type="any" required="true"&gt;
		&lt;cfscript&gt;
  		super.Init(arguments.controller);
  		setpluginName("EmbedVideo");
  		setpluginVersion("1.0");
  		setpluginDescription("A video embedding plugin that uses FlowPlayer");
  		setPluginAuthor("Brian Ghidinelli");
  		setPluginAuthorURL("https://www.ghidinelli.com");
  		setPluginURL("https://www.ghidinelli.com");

  		//Return instance
  		return this;
		&lt;/cfscript&gt;
	&lt;/cffunction&gt;

&lt; !------------------------------------------- PUBLIC -------------------------------------------&gt;	

    &lt; !--- today ---&gt;
	&lt;cffunction name="renderit" output="false" access="public" returntype="string" hint="This plugin will embed a video on the page using FlowPlayer"&gt;
		&lt;cfargument name="url"  required="true" type="string" hint="The url to the video to display" /&gt;
		&lt;cfargument name="height" type="numeric" required="false" default="300" /&gt;
		&lt;cfargument name="width" type="numeric" required="false" default="400" /&gt;
		&lt;cfargument name="splash" type="string" required="false" default="" /&gt;
		&lt;cfargument name="alt" type="string" required="false" default="" /&gt;

		&lt;cfset var event = getController().getRequestService().getContext() /&gt;
		&lt;cfset var content = "" /&gt;

		&lt; !--- use a jquery onready block ---&gt;
		&lt;cfoutput&gt;
		&lt;cfsavecontent variable="content"&gt;
			<a href="#arguments.url#"
			   class="flowplayer"
			   style="width: #arguments.width#px; height: #arguments.height#px;">&lt;cfif len(arguments.splash)&gt;&lt;img src="#arguments.splash#" height="#arguments.height#" width="#arguments.width#" alt="&lt;cfif len(arguments.alt)&gt;#HTMLEditFormat(alt)#&lt;cfelse&gt;Click to start playing&lt;/cfif&gt;" /&gt;&lt;/cfif&gt;</a>
			&lt;script language="javascript" type="text/javascript"&gt;
				$(document).ready(function()
				{
					flowplayer("a.flowplayer", "/js/flowplayer/flowplayer-3.1.2.swf");
				});
			&lt;/script&gt;
		&lt;/cfsavecontent&gt;
		&lt;/cfoutput&gt;

		&lt;cfreturn content /&gt;
	&lt;/cffunction&gt;

&lt;/cfcomponent&gt;</code></pre>
]]></content:encoded>
			<wfw:commentRss>http://www.ghidinelli.com/2009/08/13/flowplayer-plugin-for-codex-wiki/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Getting the current RequestTimeout</title>
		<link>http://www.ghidinelli.com/2009/07/22/get-current-requesttimeout</link>
		<comments>http://www.ghidinelli.com/2009/07/22/get-current-requesttimeout#comments</comments>
		<pubDate>Wed, 22 Jul 2009 21:00:06 +0000</pubDate>
		<dc:creator>brian</dc:creator>
				<category><![CDATA[ColdFusion]]></category>
		<category><![CDATA[My Software]]></category>
		<category><![CDATA[Web/Internet]]></category>
		<category><![CDATA[CFHTTP]]></category>
		<category><![CDATA[cfpayment]]></category>

		<guid isPermaLink="false">http://www.ghidinelli.com/?p=859</guid>
		<description><![CDATA[Working with ColdFusion RequestTimeout - how to get the current timeout, modify it and track it to prevent throwing an error in long running requests like CFLOOP.]]></description>
			<content:encoded><![CDATA[<p><img src="https://www.ghidinelli.com/wp-content/uploads/2009/07/rctimeout.png" alt="rctimeout" width="308" height="73" class="alignright" />In ColdFusion, each page request has a timeout that is set in the ColdFusion Administrator.  This is a global default for all requests.  It&#8217;s independent of the timeout attribute on tags like CFHTTP and covers the entire request lifecycle.  You can update it using the <a href="http://livedocs.adobe.com/coldfusion/8/Tags_r-s_18.html">cfsetting</a> tag at any point for a given request:</p>
<pre><code>&lt;cfsetting requesttimeout="300" /&gt;</code></pre>
<p>I had an issue with <a href="http://cfpayment.riaforge.org">cfpayment</a> where processing transactions in a loop was timing out.  I had even written some fancy code designed to gracefully abort before timing out but it wasn&#8217;t working.  I had set the timeout to 1800 seconds and I could see from transaction logs that it was ending right around 5 minutes, or 300 seconds, which is a common timeout setting in our payment processing code.  I went sniffing&#8230; and this is what I found.  My controller was indeed setting the timeout to 1800 seconds.  Here is a simplified version of my abort-before-it-times-out code:</p>
<pre><code>&lt;cfset timeout = 1800 /&gt;
&lt;cfset thread = CreateObject("java", "java.lang.Thread") /&gt;
&lt;cfset timer = getTickCount() /&gt;

&lt;cfsetting requesttimeout="#timeout#" /&gt;

&lt;cfloop from="1" to="15" index="ii"&gt;
	&lt; !-- I was calling cfpayment.charge() here --&gt;
	&lt;cfset thread.sleep(2000) /&gt;

	&lt;cfif (getTickCount() - timer)/1000 GT timeout * 0.9&gt;
		&lt;cfoutput&gt;breaking from loop!&lt;br /&gt;&lt;/cfoutput&gt;
		&lt;cfbreak /&gt;
	&lt;/cfif&gt;
&lt;/cfloop&gt;</code></pre>
<p>So in my case, I had far more than 15 payments to run in a batch and the timeout was 1800 seconds.  But inside of cfpayment, in the base component process() method which handles the CFHTTP code, I had this:</p>
<pre><code>&lt;cfsetting requesttimeout="300" /&gt;</code></pre>
<p>Crap.  No matter what I set the timeout to be, as soon as I charged a card, it was reset down to just 300 seconds and so my batch processing would time out.  Thanks to a <a href="http://www.barneyb.com/barneyblog/2007/08/17/requestmonitorgetrequesttimeout/">clever but woefully named</a> post by Barney Boisvert, getting the request timeout of the current context is super simple:</p>
<pre><code>&lt;cffunction name="getCurrentRequestTimeout" output="false" access="private" returntype="numeric"&gt;
	&lt;cfset var rcMonitor = createObject("java", "coldfusion.runtime.RequestMonitor") /&gt;
	&lt;cfreturn rcMonitor.getRequestTimeout() /&gt;
&lt;/cffunction&gt;</code></pre>
<p>I have updated the cfpayment core now to have the emulate the existing behavior, which is give a CFHTTP attempt 300 seconds to process a payment against a gateway, but, if we have a longer timeout already, we won&#8217;t modify it.  Now the code in base.cfc looks like this:</p>
<pre><code>&lt;cfsetting requesttimeout="#max(getCurrentRequestTimeout(), getTimeout()) + 10#" /&gt;
...
&lt;cfhttp timeout="#getTimeout()#" ... &gt;</code></pre>
<p>The clever part here is that we&#8217;re taking the longer timeout, current request context or configured timeout, and adding 10 seconds to it and then using the specified timeout for the CFHTTP request.  In my loop scenario, it will retain the longer 1800 second timeout while using a 300 second timeout for each individual payment attempt.  My controller code will also keep an eye on the overall progress and gracefully stop processing if we find less than 10% of the timeout is left.  In the more common case of processing one transaction where the request timeout and CFHTTP timeout are the same, the extra 10 seconds in the RequestTimeout will afford enough time to process the results of the timed out CFHTTP (via try/catch, cferror, onError, etc).</p>
<p>This is a strategy I&#8217;ve used for a long time in order to be able to catch and process timeouts (send an email, etc).  Ben Nadel had a post about <a href="http://www.bennadel.com/blog/916-Graceful-ColdFusion-Timeout-Disaster-Recovery-Thanks-Barney-Boisvert-.htm">graceful timeout recovery</a> in 2007 which covers this in detail and it&#8217;s an invaluable tool for potentially long running network requests that MUST have error recovery such as payment processing.  If you don&#8217;t use this technique, I recommend reading his post.</p>
<p>The good news is, if you&#8217;re using <a href="http://cfpayment.riaforge.org">cfpayment</a>, you already have it. <img src='http://www.ghidinelli.com/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.ghidinelli.com/2009/07/22/get-current-requesttimeout/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>BounceDetector 1.1 Released</title>
		<link>http://www.ghidinelli.com/2009/07/10/bouncedetector-1-1-released</link>
		<comments>http://www.ghidinelli.com/2009/07/10/bouncedetector-1-1-released#comments</comments>
		<pubDate>Fri, 10 Jul 2009 23:37:39 +0000</pubDate>
		<dc:creator>brian</dc:creator>
				<category><![CDATA[ColdFusion]]></category>
		<category><![CDATA[My Software]]></category>
		<category><![CDATA[Web/Internet]]></category>
		<category><![CDATA[bounce]]></category>
		<category><![CDATA[bouncedetector]]></category>
		<category><![CDATA[email]]></category>

		<guid isPermaLink="false">http://www.ghidinelli.com/?p=815</guid>
		<description><![CDATA[BounceDetector 1.1 Released.  Analyze bounced email and determine severity and cause using extensive signature database.]]></description>
			<content:encoded><![CDATA[<p>I updated my <a href="http://bouncedetector.riaforge.org/">BounceDetector</a> project over at RIAForge last night to version 1.1.  You can use this simple two-file package to analyze the content of bounced email and determine severity and cause using an extensive production-tested signature database.  Paired with CFPOP, this lets you easily fulfill <a href="http://www.catb.org/~esr/jargon/html/Z/Zawinskis-Law.html">Zawinski&#8217;s Law</a>.  New in this release:</p>
<ul>
<li>Changed reFind() to find() for a (substantial) speed increase as well as more precise signature matching.  The signatures in the database weren&#8217;t intended as regular expressions previously.  I began the process of converting the many signatures into fewer RegExps but found that it made it more difficult to determine if a signature was in the database which would make future updates more difficult.  The find() method is fast enough to make improving the reFind() method unnecessary.</li>
<li>Removed some duplicate signatures</li>
<li>Added new signatures collected from our production system</li>
<li>Reorganized signatures slightly to put common numeric-based reasons at the top which will increase the likelihood of an early match and exit from the loop.</li>
</ul>
<p>I renamed the files as well to reduce the likelihood that it would conflict with your existing systems.  The goal was to make it easier to use as an svn:external which is how I&#8217;m including it in my applications now.  </p>
]]></content:encoded>
			<wfw:commentRss>http://www.ghidinelli.com/2009/07/10/bouncedetector-1-1-released/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
	</channel>
</rss>
