ColdFusion Per-Application Datasources Don't Work In The OnApplicationEnd() Event Handler
After yesterday's blog post about the limitations of the onApplicationEnd() event handler in ColdFusion, I wanted to see if the per-application datasources fell pray to the same problem. Long story short, they do. And, while I haven't tested all of the options, it is becoming clear to me that none of the per-application settings work in the onApplicationEnd() event handler.
To test this, I created a super simple data access component that can log messages to a database table. You'll notice that I have omitted the "datasource" attribute of the CFQuery tag. This is because it is meant to use the per-application datasource that was introduced in ColdFusion 9.
<cfcomponent
output="false"
hint="I am testing data access using per-application datasources.">
<cffunction name="logMessage" access="public" returntype="void" output="false">
<!--- Define arguments. --->
<cfargument name="message" type="string" required="true" />
<cfquery name="local.result">
INSERT INTO message_log
(
message,
createdAt
) VALUES (
<!--- message. --->
<cfqueryparam value="#message#" cfsqltype="cf_sql_varchar" />,
<!--- createdAt. --->
<cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp" />
);
</cfquery>
</cffunction>
</cfcomponent>
Now, in the Application.cfc ColdFusion framework component, I'm creating and caching the above logger in the onApplicationStart() event handler. I then log the application setup and application teardown events:
<cfscript>
component
output = false
hint = "I define the application settings and event handlers."
{
// Define the application settings.
this.name = hash( getCurrentTemplatePath() );
// We're going to keep the application timeout really short since we are just
// demonstrating the interplay between the onApplicationEnd() event and the use
// of per-application mappings.
this.applicationTimeout = createTimeSpan( 0, 0, 0, 5 );
// Define the per-application datasource.
this.datasource = "testing";
// Define the per-application mappings.
this.mappings[ "/" ] = getDirectoryFromPath( getCurrentTemplatePath() );
this.mappings[ "/root" ] = this.mappings[ "/" ];
this.mappings[ "/logs" ] = ( this.mappings[ "/root" ] & "logs/" );
this.mappings[ "/wwwroot" ] = ( this.mappings[ "/root" ] & "wwwroot/" );
// ---
// PUBLIC METHODS.
// ---
/**
* I setup the application.
*
* @output false
* @hint I will prevent the application from loading if you return False.
*/
public boolean function onApplicationStart() {
application.messageLogger = new models.MessageLogger();
application.messageLogger.logMessage( "Setting up application state." );
return( true );
}
/**
* I teardown the application, consuming any remaining data.
*
* @applicationScope I am the application scope of the application that ended.
* @output false
*/
public void function onApplicationEnd( required any applicationScope ) {
// In the application-end (and session-end) event, we have to catch our own
// errors since they will not be natively logged by ColdFusion.
try {
applicationScope.messageLogger.logMessage( "Tearing down application state." );
} catch ( any error ) {
logItem( "onApplicationEnd Error", error );
}
}
// ---
// PRIVATE METHODS.
// ---
/**
* I log the given value to the log file (using cfdump).
*
* @label I am the label of the value (NOTE: Does not get logged in this version).
* @value I am the value being logged.
* @output false
*/
private void function logItem(
required string label,
required any value
) {
writeDump(
var = value,
label = label,
output = ( this.mappings[ "/logs" ] & "log.txt" ),
format = "text"
);
}
}
</cfscript>
Super simple code. When the application starts up, I do get see the "Setting up application state." in the message log. But, when the application ends, I don't see the teardown message. Instead, I get the following error logged to the txt file:
Attribute validation error for tag CFQUERY. The value of the attribute datasource, which is currently '', is invalid.
The problem is that the MessageLogger.cfc doesn't use the datasource attribute since it's relying on the per-application datasource. But, the per-application datasource doesn't work in the onApplicationEnd() event handler; so, ColdFusion doesn't know which datasource to use. It is important to understand that using this feature (per-application settings) has nothing to do with the time at which the ColdFusion component was instantiated - only the time at which it was invoked.
The more I dig into this, the more I realize that the onApplicationEnd() event handler is fairly useless. And, just to take a step back and get a little perspective, you probably shouldn't rely on your onApplicationEnd() to do much anyway. After all, if you application is "successful," your application should never end. Meaning, you should have sufficient traffic to ensure the application never times-out. And, at that point, you have to find a different means to do periodic cleanup, such as with a scheduled task.
Want to use code from this post? Check out the license.
Reader Comments