Yesterday I ran into a very strange bug with ColdFusion 9 and I thought it worth blogging about. I think this probably affects earlier versions of the product, but I haven't tested to confirm.
What was happening is whenever I tried executing a specific query, I was seeing the following error:
Invalid data coldfusion.sql.QueryColumn@540350 for CFSQLTYPE CF_SQL_INTEGER
The error had me very perplexed, because the variable that it was complaining about I knew was an integer. When displayed on screen, it showed as an integer. It would even return true when passed to the isNumeric() function. After spending way to much time on the issue, I finally track down the root problem.
What was happening is the variable's value was coming from a ColdFusion query, that I was converting to a structure. Since the query was designed to return at most a single row, I was using the shortcut notation of queryName.columnName to update the variable. I've used this shorthand plenty in the past, because it will either display the value in the first row (when not inside a <cfoutput query=""> or <cfloop query="">) or it will display an empty string if the query returned no rows. This has generally worked fine for me, but apparently this ends up storing a reference to the query object, instead of copying the value directly—which <cfqueryparam /> did not like.
The fix was pretty straightforward, all I need to do was to change my code to reflect grabbing the value from the first row of the dataset: queryName.columnName[1].
My original code looked like this:
<cfquery name="data" attributeCollection="#Application.dsn.getAttributes()#"> select Name, Email, Phone from Employee where EmployeeId = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.EmployeeId#" /> </cfquery> <!---// get the column names from the query //---> <cfset columns = getMetaData(data) /> <!---// return the preferences as a struct //---> <cfloop index="column" array="#columns#"> <cfset results[column.Name] = data[column.Name] /> </cfloop>
All I did was change the code to:
<cfquery name="data" attributeCollection="#Application.dsn.getAttributes()#"> select Name, Email, Phone from Employee where EmployeeId = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.EmployeeId#" /> </cfquery> <!---// get the column names from the query //---> <cfset columns = getMetaData(data) /> <!---// return the preferences as a struct //---> <cfloop index="column" array="#columns#"> <cfset results[column.Name] = data[column.Name][1] /> </cfloop>
NOTE:You'll notice the only change is the [1] after data[column.Name].
What I learned from this is that I shouldn't trust using the queryName.columnName shorthand—at least not outside a cfoutput/cfloop query block. Instead I need to make sure to reference an actual row from the query.
A common practice when building applications in ColdFusion is to utilize the onError event in the Application.cfc in order to track and log errors that occur in your application, so that you can track down the problems and resolve them. However, there's one type of error that can often escape your onError event handler—and that's requests that are timing out.
First, just some background information on how ColdFusion handles "request timeouts". If the server (or current page) is designed to timeout after 30 seconds, ColdFusion will not simply stop executing when the length of the running request gets to 30 seconds. Instead there are specific operations in ColdFusion1 that check the current running time to see if the request should be halted. That's why if you have a SQL query that takes 45 seconds to run, the page doesn't simple stop after 30 seconds. Instead the query will finish executing and your code won't halt execution until it tries to execute logic that would check the current execution runtime.
NOTE:This also why when ColdFusion reports the line that took to long to run, it often isn't pointing to the actual line of code that was the real culprit, but a tag like <cfoutput>—which is just displaying the information.
Now that you hopefully have a better understanding of when request timeouts are thrown, let's examine why the Application.onError might not run.
The problem isn't that the Application.onError event doesn't get fired—it does. The problem is that because your page has already been running longer than the allotted time, as soon as ColdFusion encounters one of the operations that checks to see if the page should timeout, it will throw a second error—which effectively breaks your onError event.
The way you can get around this problem is by tracking the current execution timing in your Application and then having your Application.onError immediately adjust the page's request timeout setting as it's first line of logic. Since the <cfsetting /> tag does not check against the current execution time, this allow you to add a buffer to your onError request so that the event can run to completion.
Here's a sample snippet of an Application.cfc which will allow the Application.onError event to run for another 10 seconds—regardless of how long the current template has been running:
<cfcomponent output="false"> <!---// track the starting execution time //---> <cfset executionStartTime = getTickCount() /> <!---// onError //---> <cffunction name="onError" returnType="void" output="true"> <cfargument name="exception" type="any" required="true" /> <cfargument name="eventName" type="string" required="true" /> <!---// declare local variables //---> <!---// take the current time the request has been running and add 10 seconds, to attempt to run the onError handler succesfully //---> <cfsetting requesttimeout="#(((getTickCount()-executionStartTime)/1000)+10)#" /> <!---// insert error code handling here //---> </cffunction> </cfcomponent>
I've been using this trick to help make sure any requests that are timing out are fully logged, so I can evaluate the issue and look for ways to fix the root problem.
1 Unfortunately I do not have a list of which operations in ColdFusion do an integrity check on the request lifecycle. I do know that <cfloop>, <cfoutput>, <cfquery> and most complex cf-based tags do check the current running time against the page's request timeout setting.
Yesterday I finally had the incentive to download Git so I could fork a Github project (Mustache.cfc) and contribute some changes I'd made. Being that Eclipse is my main IDE, EGit seemed to be a logical client to install. The install process was painless and I was able to figure out how to clone a local copy, commit and push changes back to Github. Even the Github pull request process was painless. Much to my chagrin, my pull request was accepted almost immediately and merged back into the main branch.
Naturally the next thing I wanted to do was to sync my fork with the original repository. This is where I got stuck. Whether due to my lack of understanding of Git terminology or just a lack of my Google Search skills, I was not able to easily find instructions for how to sync the original repository back to my fork. After much searching, I was finally able to find some help.
However, I figured I'd clean up the instructions a bit and provide a little more detail on the steps for merging the main repository back into your fork.
The first thing you need to do, is set up a new remote repository linked to your fork:
Now that you have a linked the original repository to your fork, you can merge changes from the original repository to your fork:
These steps worked for me, so hopefully they'll help guide someone else!
This morning I was reading Adding your own callbacks to existing JavaScript functions by Dave Ward—which covers how to overwrite an existing function so you can add some additional functionality (in this case, adding callbacks.) While the article is informative, a couple of improvements can dramatically improve his suggestion.
If you don't want to take the time to read Dave's article, in a nutshell he describes how we can overwrite a JavaScript function by storing a reference to the original function in a variable. So, we can take the following function:
function sayHello(name){ alert("Hello, " + name + "!"); }
And we can now overwrite that function by storing a reference to the original function in a variable:
var sayHelloOld = sayHello; function sayHello(){ var name = prompt("Enter your name"); sayHelloOld.apply(this, [name]); }
Now there's a couple of problems with the above code.
We can solve both those problems by using a closure around our code:
// define a closure and pass in a reference to the global window object (function (w){ var sayHelloOld = w.sayHello; w.sayHello = function (){ var name = prompt("Enter your name"); sayHelloOld.apply(this, [name]); } })(window || {});
(NOTE: You can see a working copy on JSFiddle.)
The other topic Dave discusses is how to add callback hooks to run before and after a the original function code runs. His suggestion is built around using the global name space to declare some function names. Since the example is based around jQuery, I'd suggest a much better method would be to add in custom events to your function. This gives you a way to bind callbacks to run, but your neither cluttering the global namespace nor running into issues if the callbacks aren't needed.
Here's what Dave's original solution looks like:
var oldTmpl = jQuery.fn.tmpl; // Note: the parameters don't need to be named the same as in the // original. This could just as well be function(a, b, c). jQuery.fn.tmpl = function() { if (typeof onBeforeTmpl === 'function') onBeforeTmpl.apply(this, arguments); // Make a call to the old tmpl() function, maintaining the value // of "this" and its expected function arguments. var tmplResult = oldTmpl.apply(this, arguments); if (typeof onAfterTmpl === 'function') onAfterTmpl.apply(this, arguments); // Returning the result of tmpl() back so that it's actually // useful, but also to preserve jQuery's chaining. return tmplResult; };
Using the two previously mentioned techniques combined, here's how I'd change that code:
(function ($){ var oldTmpl = $.fn.tmpl; // Note: the parameters don't need to be named the same as in the // original. This could just as well be function(a, b, c). $.fn.tmpl = function(){ // trigger the before callback // to attach a callback, we just bind() this custom event to our jQuery object this.trigger("onBeforeTmpl", arguments); // Make a call to the old tmpl() function, maintaining the value // of "this" and its expected function arguments. var tmplResult = oldTmpl.apply(this, arguments); // trigger the after callback // to attach a callback, we just bind() this custom event to our jQuery object this.trigger("onAfterTmpl", arguments); // Returning the result of tmpl() back so that it's actually // useful, but also to preserve jQuery's chaining. return tmplResult; }; })(jQuery || {});
This gives use a few benefits over Dave's original solution:
To use our code, we can do:
$("#id") .bind("onBeforeTmpl", function (){ alert("before!"); }) .bind("onAfterTmpl", function (){ alert("after!"); }) .tmpl(data, options, parentItem);
NOTE:If your prefer to run the same callbacks for all $.tmpl() calls, you could attach the custom events globally.
Any comments on how to make this solution even better?
Today a new version of the jQuery Linkselect Plug-in was released. The new version under went a ton of changes from the previous version and includes a much better CSS skinning mechanism (see the Linkselect Example.)
The coolest new feature is probably the "placeholder" support, which allows you to specify an <option /> element in your <select /> box to use as a label/placeholder. The "placeholder" <option /> tag uses the <option /> tag as the title of the dropdown menu that's always visible, but still selectable. Here's an example:
Here's a list of all the changes:
We recently upgraded our version of Subversion from a very old version of Subversion to v1.7.1. We really wanted the newer merging capabilities that arrived in Subversion 1.5 and figured it was worth the energy to upgrade Subversion to the latest version. Fortunately upgrading our Subversion repositories went very smoothly. I even decided to just do a full dump/load to make sure the repositories were fresh 1.7.x repositories. While this process took a little extra time, the upgrade itself went very smoothly.
However, where things really fell apart for us was with our Trac/SVN integration. The problem is we were still running Python 2.4, which include old SVN bindings, so any attempt to do anything with our SVN ended up with an error that looked like:
TracError: Couldn't open Subversion repository c:/path/to/repository: SubversionException: ("Expected FS format '2'; found format '4'", 160043)
In order to get the SVN commit hooks working with Trac, I was going to need SVN bindings that worked with SVN v1.7.1, but also were compiled for my version of Python. While one might think this wouldn't be difficult, I couldn't find any SVN bindings for Python 2.4. This left my options at either trying to compile my own versions or upgrade Python. Since I don't have easy access to tools for compiling my own version of the Windows binaries, I thought the best option was just to upgrade Python—especially since Trac is soon dropping support for Python 2.4.
The problem is there's no easy way to just migrate a Trac install to a new version of Python, because you're going to need to recompile all your Python *.egg files for the version of Python you're using. After spending way too much time getting all this working, I thought it was wise that I compile a list of all the steps you may need to take in order to get Trac migrated to a new version of Python.
Here is the general install instructions. I don't break this down as a true step-by-step instructions, because so much varies based on your environment. However I do break down all the major steps you'll need to take and try to point you to step-by-step instructions for each bullet when possible.
If all goes well, your environment should now be running under your new Python installation.
This process ended up costing me way more time than expected, so hopefully this guide will prevent you from struggling the way I did.
I've been updating some code that was built using jQuery Templates, but since jQuery Templates has been discontinued, I've decided to migrate my code to use JsRender.
The syntax is close enough for the most part that translating existing templates is straightforward enough. For the most part it's updating to the new mustache-like bracket syntax and changing variables from ${var} to {{=var}}. However, there was one thing that was tripping me up—simple conditional statements.
JsRender uses the mustache like "codeless" syntax for conditions:
{{#if varName}}output{{/if}}
This fine if varName is a truthy statement, but what if you need to check if varName is equal to a specific value? Having gone through all the JsRender sample code, it appeared the only way to accomplish any kind of comparison conditional statement was to use custom code. This syntax ends up being very ugly and verbose. Here's an example of writing an attribute dynamically based on the value of a variable named varName :
<div class="base{{* if($view.data.varName == 'None'){ result += ' none'; } }}">{{=varName}}</div>
This seems overly complicated for such a simple comparison. I figured there had to be a better way.
After digging through the code, I found there's some undocumented features of the if tag. You can actually use the following syntax to do the same thing:
<div class="base{{#if varName eq='None')}} none{{/if}}">{{=varName}}</div>
While this syntax ends up being pretty close to jQuery Templates, it uses the "hash" functionality of JsRender's custom tags. The odd looking part is that you need the equal sign between the conditional operator and the value. Other operators include:
I actually would prefer to see "lte" and "gte" used for the "or equal" functions, so I've added that to a ticket.
So, while the syntax isn't ideal, it's certainly better than using using the code syntax. Since the project is still in development, it possible the syntax will change.
On Friday I upgraded my iPhone 3GS to the new iPhone 4S. One of the things that's changed in the 4S is that it's supposed to support HSPA+. Well not truly "4G", it should offer faster data transfers on AT&T's network. Before heading into the AT&T store to pick up my new phone, I decided to run a couple of tests on my 3GS in my car while parked in the parking lot.
My initial tests on the 3GS reported data xfer speeds around 970Kbits/s.
Once I had my new 4S in hand, I returned to my car to repeat my performance tests. I immediately saw the numbers jump to somewhere in the 1820Kbits/s—almost double the speed.
While this was not an exhaustive test by any means, it does appear that there is a nice speed jump on AT&T's network—provided your in an area covered by HSPA+.
Here's the screenshot showing my min/max results:
There was some disturbing news yesterday—and I'm not talking about the death of Steve Jobs. The "Olson" time zone database was taken offline. While the immediate impact may not be felt, this is a very important open project and shutting it down is potentially going to cause a lot of problems.
For those that don't know, the time zone database has been maintained by a group of volunteers over the years and hosted at National Institutes for Health and contains historical, present day and known future information on day light savings changes around the world. This database is used by a huge of array of platforms to keep accurate time—Java, Unix, *nix, smart phones, etc. If you have an app that can tell you the current time in another location, there's a good bet it's based on this time zone database (either directly or via the underlying language's platform.)
Here are some good articles with more insight:
As many have already commented on, I'm not sure how this law suit can hold up since historical information can not be copyrighted. However, the law suit will have an impact—especially for applications in use by locations where the day light savings is in frequent flux.
One of the features introduced in Microsoft SQL 2005 that I think really goes largely unused in the "OUTPUT" clause. It certainly was a feature that went unnoticed by me for a long time and even once I became aware of the feature, didn't really put it to much use. However, lately I've been refactored a ton of old SQL into objects and with this refactoring I've been making high use of the OUTPUT clause in order to help create audit trails. It's been a great way to know which rows were affected in your CRUD operations.
There's really a ton of useful things you can do with the OUTPUT clause, but one thing it allows you to do is to migrate data from one table to another in a single atomic operation.
Let's look at the syntax:
-- the table to move data from delete dbo.SourceTable -- get the data removed from the "source" table output deleted.column1, deleted.column2, deleted.column3 -- insert the deleted row into the "destination" table into dbo.DestinationTable -- your where clause for the "delete" operation where Column1 > 2000 and Column2 = 1234
Here's a fully working example that you can run in SSMS:
-- declare temp tables declare @original table (id int identity(1,1), name varchar(20), company varchar(20), dob datetime) declare @new table (id int, name varchar(20), company varchar(20), dob datetime) -- insert some fake data into our "original" table insert into @original (name, company, dob) values ('Bill Gates', 'Microsoft', '1955-10-28') insert into @original (name, company, dob) values ('Paul Allen', 'Microsoft', '1953-01-21') insert into @original (name, company, dob) values ('Steve Jobs', 'Apple', '1955-02-24') insert into @original (name, company, dob) values ('Steve Wozniak', 'Apple', '1950-08-11') -- show the results select 'original' as tableName, * from @original select 'new' as tableName, * from @new /* here is the core SQL used to do the move */ delete @original -- get the data removed from the @original table output deleted.id, deleted.name, deleted.company, deleted.dob -- insert the deleted row into the @new table into @new -- your where clause for the "delete" operation where company = 'Microsoft' -- show the new results select 'original' as tableName, * from @original select 'new' as tableName, * from @new
If you run this example in SSMS, you'll see the two "Microsoft" records move from the @original table into the @new table.
NOTE:The @original and @new tables are simple table variables (which are like temp tables.)
There really is a ton of useful things you can do with the OUTPUT clause, so I highly recommend playing around with the OUTPUT clause if you're using SQL Server 2005 or later!
[UPDATED: Wednesday, August 24, 2011 at 3:19:27 PM]
I was looking through the source code of Underscore.js this morning and notice it's implementation of _.throttle() contains two issues that bother me with most JavaScript-based throttle implementations I've seen:
Let's take a look at an example I posted on JSFiddle: http://jsfiddle.net/MNGpr/
Mouse your mouse from the top left of the "Results" frame to the bottom right. If you do this quickly (under the 1 second delay,) you'll notice that when the function executes it's based upon coordinates in the upper left of the screen—not where your cursor left off.
What I've been doing is using the following throttle() function in my code:
// limit a function to only firing once every XX ms var throttle = function (fn, delay, trail){ delay || (delay = 100); var last = 0, timeout, args, context, offset = (trail === false) ? 0 : delay; return function (){ // we subtract the delay to prevent double executions var now = +new Date, elapsed = (now - last - offset); args=arguments, context=this; function exec(){ // remove any existing delayed execution timeout && (timeout = clearTimeout(timeout)); fn.apply(context, args); last = now; } // execute the function now if( elapsed > delay ) exec(); // add delayed execution (this could execute a few ms later than the delay) else if( !timeout && trail !== false ) timeout = setTimeout(exec, delay); }; };
What this version does that differentiates it from the Underscore.js version is it:
To see the difference in behavior, check out the JSFiddle example using my version of throttle().
I'm working on migrating our application to use the latest version of jQuery. In the process of migrating, I've been working on fixing compatibility issues with several of the plug-ins I've worked on to make sure they work with the latest version of jQuery. So, here's a list of updated plug-ins and their release notes:
[UPDATED: Tuesday, June 21, 2011 at 10:27:35 AM]
About 2 years ago, I decided to take the plunge and see what the iPhone craze was all about. My contract was over with my Windows Mobile phone and I was looking for something to replace it. While I loved the idea of my Windows Mobile phone (WM5,) it's execution was severely lacking and I pretty much had non-stop issues with it. I experienced all sorts of random lockups, services shutting off, etc. in my 2 years of owning the VX-6700 phone. While it tried to combine the power of a PDA and a Phone, it really did neither of them well (but it was probably a better PDA than it was a Phone—it was a horrible phone.)
So, even with some of the negatives I'd read about the iPhone, I was definitely anxious to try a smart phone that seemed to be both a solid PDA and a phone—even if it was tied to iTunes and AT&T's network.
It didn't take long to discover the iPhone was vastly superior to my old WM phone. The UI was intuitive and the device worked. Battery life was excellent and my only real issues with the phone were it's lack of a physical keyboard and it's dependency on iTunes—which I despise.
Fortunately, once I found MediaMonkey I only needed to rely on iTunes for the occasional backup and firmware updates. Using MediaMonkey does end up requiring some patience, because Apple is constantly trying to prevent 3rd party applications from managing songs on the iPhone/iTouch/iPad devices, but if you're willing to wait for the developers of MM to work out the issues w/each firmware revision, the software works well and fits my setup much better than iTunes ever will.
After 2 years of using my iPhone, I've been extremely happy, but there are a few points that have always bothered me:
So, in my search to find the best phone I can, I thought I'd try my hands at an Android-based phone. I've known plenty of people who have gone from iPhones to an Android device and they seemed pretty happy. I was also wanting to get off AT&T and get back to Verizon Wireless—since they have without a doubt have a better network than AT&T.
After much research, the wife and I decided on the Samsung Droid Charge. We played around with several phones at the Verizon store, but the wife and I both really dug the display on the Droid Charge and I was really digging Swype when playing around with it at the store. I also wanted to make sure I got a 4G phone, since I was getting locked into another 2 year contract. I ended up buying the phone from Amazon Wireless, because at the time they had the phone for half the price I could buy it directly from Verizon—which means I'd get both phones for the price of one phone directly from Verizon.
The phones took 2 business days to arrive after ordering and I eagerly opened the packages so I could begin charging the phones so I could play with them after work. After playing with the phone for a week, here's how I view the Android experience.
My initial opinion of the Android after playing it with it for a day was really poor. I constantly felt lost in applications, not sure how to perform actions that were always very intuitive on the iPhone. I found navigating an app with the separate buttons difficult and not intuitive at all. I had become so accustom to iPhone apps offering all the options onscreen, that go "back" in an app or trying to refresh a listing by hitting the "menu" button was not very intuitive.
Also, actions that were single clicks/actions on the iPhone, generally involved at least 2 operations on the Android. Want to refresh your mail on the iPhone? Click the refresh button on the screen. That's one action. Want to refresh your mail in Android? Press the menu button, click refresh. That's two actions. Pretty much ever application I use frequently on the iPhone is like that. The Android way pretty much always involves more steps—many of them which were not intuitive.
I also started really missing the ability to scroll to the top of a page by clicking in the status bar—which works in pretty much every iPhone app where the page scrolls. I've not found an equivalent operation in Android, and this makes using some Android apps painful—especially the browser.
After the first day, I was really hating the phone, but I was intent on trying to give the phone a fair chance and wanted to use the phone for a week before making any rash decisions. So I kept plugging away, trying to use the phone as my primary device.
I started liking the phone better after the second day of usage as I began to get the hang of how the phone worked. It's still nowhere near as intuitive or as efficient as the iPhone is, but at least I wasn't feeling lost any more. Maybe this Android thing isn't so bad after all!
However, the more I used the phone, the more I realized the many deficiencies in the Android platform. I've yet to find find a program on Android that's better than it's iOS equivalent. The Facebook app for iOS is way better than the one on Android and I'm pretty much seeing that trend across all the applications I've downloaded. The one exception might be Words for Friends—only because it tells you what word is invalid (but the lack of an Ad-free version makes it worse overall.)
The battery life is also abysmal compared to my iPhone 3GS. I know the screen and increase in CPU power both will affect things, but even when the phone is in an idle state the battery drain is incredible. After playing around with some task killer applications, I can at least get a day of moderate use out of the phone, but there's no way the battery would last for 2 days—even if I wasn't using it. My iPhone on the other hand can go a couple of days easy on one charge w/moderate use.
This leads me to my biggest complaint with Verizon. One of the reasons I left Verizon is because their phone selection was always poor. They never had phones that came close to their competitors. However, in the 2 years I've been with AT&T they've actually started getting much better phones. The problem is, they tend to be slow to update the firmware on the phones and their firmware is loaded with Verizon crap that you can't install.
Whenever I run Advanced Task Killer, I constantly see Verizon apps re-spawning that I have no intention of every running (like IM, Daily Briefing, My Verizon Mobile, etc.) Obviously running programs that aren't going to be used affects both battery, but performance. Please let me remove programs I have no intention on using.
The other issue is obviously with outdated firmwares. The Samsung Droid Charge is only like a month old—it's one of Verizon's newest phones. Yet the it's still shipping with Froyo 2.2.1. Why does Verizon always wait so long to release firmware updates? Android 2.3.4 came out in May 2011, but Android 2.3's been out since December 2010. That's not to mention that Android 3.0 was released in February and 3.1 is soon to be released.
So, after a week of using Android I'm really thinking I'm going to abandon the Android device and go back to the iPhone.
It's not all bad though, there are some things I really do like and love about the Samsung Droid Charge.
I really, really wanted to love this Android device, but the more I use it the more I realize the iPhone is superior for me. It appears many of my issues may be addressed in newer versions of Android OS, but Verizon and especially Samsung have a bad history of basically abandoning firmware updates after 6 months of a phone being released. I'm sure that Gingerbread will eventually be released for the Charge, but I'm beginning to doubt whether I'll ever see 3.0 or 3.1 officially released for the phone—which is a real shame. These mobile carriers are in such a hurry to release a new crop of phones every 6 months, that they stop improving their existing phones. If the carriers are going to lock you into a 2 year contract, then they out to be guarantee that they will keep the phone up-to-date with the latest firmwares over that 2 year period.
I'm going to touch base with Verizon today to see if they can resolve my issues. I've got another week before my 2 week trial is up and I really want to give the phone a chance—I'm really trying. However, at this point I think I'm going to stick with an iPhone and iOS (and thus, probably staying w/AT&T. As much as I'd love to be on Verizon's network, but it bothers me that Verizon's version of the iPhone doesn't seem to be keeping up with the firmwares.)
This morning I was working on some code where I wanted to capture an error in side a cftry/cfcatch block and rethrow the message, but I wanted to modify the cfcatch.message key to include more detail without losing the stack trace. I could have just used the <cfthrow /> tag and used the type, message and detail attributes but this would have caused me to lose the stack trace—which I needed.
Idealistically, I could have just modified the "message" key in the cfcatch variable, but the problem is the ColdFusion cfcatch variable is not really a struct—it looks like one—but it's actually an Java exception. Because it's not really a struct, it's not possible to just edit key values—making the cfcatch variable essentially read-only.
This got me looking into a method were I could rethrow the original stack trace, but customize the "message" key. After a few minutes playing around the java.lang.Exception object, I came up with the following:
<cffunction name="rethrowMessage" access="public" returntype="void" output="false" hint="Rethrow a CFCATCH error, but allows customizing the message key"> <cfargument name="cfcatch" type="any" required="true" /> <cfargument name="message" type="string" required="false" /> <cfset var exception = "" /> <cfif not structKeyExists(arguments, "message")> <cfset arguments.message = arguments.cfcatch.message /> </cfif> <cfset exception = createObject("java", "java.lang.Exception").init(arguments.message) /> <cfset exception.initCause(arguments.cfcatch.getCause()) /> <cfset exception.setStackTrace(arguments.cfcatch.getStackTrace()) /> <cfthrow object="#exception#" /> </cffunction>
Using this UDF, I can now do something like:
<cfscript> // connect and authenticate try { store.connect(variables.instance.server, variables.instance.username, variables.instance.password); } catch(Any e) { // throw original message, but append the username to the message rethrowMessage(cfcatch=e, message=e.Message & " [#variables.instance.username#]"); } </cfscript>
This allows me to throw the original Java error that occurs when connecting to a JavaMail Store occurs, but modify the message to include the mailbox I'm trying to connect to which is helpful in debugging the root cause.
For a while now I've been dealing with issues adding/updating events to my Google Calendar from Thunderbird/Lightning. When ever I'd try to update an event, I'd get a very generic "MODIFICATION_FAILED" error message with no detailed message. I could view events just fine and even dismiss alarms, but nothing I tried would allow me to add new events directly from Thunderbird. I ended up resorting to opening up Google Calendar and adding the events via the web interface. While this works, it's no where near as fast as accepting an invite from Thunderbird.
Today I had several GoToMeeting invites that I need to add to my calendar and decided look into the problem again. After doing some searching, I found lots of threads of people having the same issue, but alas no solutions. The one thing that did keep popping up is that Google considers CalDAV support still experimental, so problems may occur.
This got me to look into if there were other solutions other than CalDAV and I came across Provider for Google Calendar 0.7.1 add-on for Sunbird and Lightning. After changing all my calendars to use the new provider, everything seems to be working very well.
So, if you're getting errors updating your Google Calendar using CalDAV, you might try the Provider for Google Calendar to see if it fixes your issues. I'd also recommend taking a look at the Calendar:GDATA Provider wiki page which covers installation/setup of the provider.