Skip to content

Instantly share code, notes, and snippets.

@bennadel
Created March 25, 2014 11:22
Show Gist options
  • Save bennadel/9759813 to your computer and use it in GitHub Desktop.
Save bennadel/9759813 to your computer and use it in GitHub Desktop.
Creating A Simple ColdFusion Cache With Java Soft-References
<cfcomponent
output="false"
hint="I handle soft reference caching using Java's java.lang.ref.SoftReference class.">
<cffunction
name="init"
access="public"
returntype="any"
output="false"
hint="I initialize the component.">
<!---
Create our internal cache. Cache entries will be stored
in this struct by key-index.
--->
<cfset variables.cache = {} />
<!--- Return this object reference. --->
<cfreturn this />
</cffunction>
<cffunction
name="cacheData"
access="public"
returntype="any"
output="false"
hint="I put the given item in the cache for the optional amount of time.">
<!--- Define arguments. --->
<cfargument
name="key"
type="string"
required="true"
hint="I am the data key used to index this cache entry."
/>
<cfargument
name="data"
type="any"
required="true"
hint="I am the data item being cached."
/>
<cfargument
name="cacheUntil"
type="string"
required="false"
default=""
hint="I am the optional timespan for caching."
/>
<!--- Define the local scope. --->
<cfset var local = {} />
<!--- Create the cache item. --->
<cfset local.cacheItem = {
data = arguments.data,
cacheUntil = arguments.cacheUntil
} />
<!---
Create a soft reference cache entry such that the
ColdFusion garbage collection can clear this pointer
if it is necessary to free up some RAM space.
--->
<cfset local.cacheEntry = createObject(
"java",
"java.lang.ref.SoftReference"
).init( local.cacheItem )
/>
<!--- Store the entry in our internal cache. --->
<cfset variables.cache[ arguments.key ] = local.cacheEntry />
<!--- Return this object reference for chaining. --->
<cfreturn this />
</cffunction>
<cffunction
name="deleteData"
access="public"
returntype="any"
output="false"
hint="I delete the cache entry at the given key.">
<!--- Define arguments. --->
<cfargument
name="key"
type="string"
required="true"
hint="I am the key of the item being checked."
/>
<!---
Delete the key. It doesn't much matter if it exists at
this point as structDelete() won't throw an error for
non-existing keys.
--->
<cfset structDelete( variables.cache, arguments.key ) />
<!--- Return this object reference for chaining. --->
<cfreturn this />
</cffunction>
<cffunction
name="getData"
access="public"
returntype="any"
output="false"
hint="I return the cached data (or null if the given cache item doesn't exist).">
<!--- Define arguments. --->
<cfargument
name="key"
type="string"
required="true"
hint="I am the key of the target cache entry."
/>
<!--- Define the local scope. --->
<cfset var local = {} />
<!---
Check to see if the cached item even exists in our
local cache.
--->
<cfif !structKeyExists( variables.cache, arguments.key )>
<!--- The cache entry could not be found. --->
<cfreturn />
</cfif>
<!---
If we have gotten this far, the cache entry exists.
However, it is possible that it doesn't truly exist
(the soft reference may have been garbage collected).
Get the cache item into the local scope.
NOTE: Wrap this in a Try/Catch since there is a slight
race condition between the previous key check and this
key reference.
--->
<cftry>
<!--- Get the cache item from the cache entry. --->
<cfset local.cacheItem = variables.cache[ arguments.key ].get() />
<!--- Catch any errors. --->
<cfcatch>
<!---
The cache item was expired between the key
check and the get() method call. Return null.
--->
<cfreturn />
</cfcatch>
</cftry>
<!---
Check to see if the cache item was garbage collected and
has also not expired (based on the cacheUntil date).
If it was then the previous get() call will have deleted
the given local variable reference.
--->
<cfif (
structKeyExists( local, "cacheItem" ) &&
(
!isNumericDate( local.cacheItem.cacheUntil ) ||
(local.cacheItem.cacheUntil gte now())
))>
<!--- Return the cached data. --->
<cfreturn local.cacheItem.data />
<cfelse>
<!---
The cache item was garbage collected or the
cacheUntil property has been surpassed. In
either case, let's clear out the soft reference
from our cache.
--->
<cfset structDelete( variables.cache, arguments.key ) />
<!--- Return null. --->
<cfreturn />
</cfif>
</cffunction>
</cfcomponent>
<!--- Get a reference to the cacher. --->
<cfset cache = application.cache />
<!--- Check to see if we should clear this cached item. --->
<cfif structKeyExists( url, "clear" )>
<!--- Delete the cached data. --->
<cfset cache.deleteData( "date" ) />
</cfif>
<!--- Get the cached date. --->
<cfset cachedDate = cache.getData( "date" ) />
<!---
Since the cached date might not be cached yet OR may have expired
OR may have been garbage collected, let's check to see if the
previous getDate() method call returned null (removing the local
variable reference).
--->
<cfif !structKeyExists( variables, "cachedDate" )>
<!--- The date needs to be re-cached. Create the raw value. --->
<cfset cachedDate = now() />
<!--- Cache the date value. --->
<cfset cache.cacheData(
key = "date",
data = cachedDate,
cacheUntil = dateAdd( "s", 10, now() )
) />
</cfif>
<cfoutput>
<p>
Now: #timeFormat( now(), "hh:mm:ss TT" )#
</p>
<p>
Cached Now: #timeFormat( cachedDate, "hh:mm:ss TT" )#
</p>
<p>
<a href="#cgi.script_name#?clear=1">Clear Cache</a>
</p>
</cfoutput>
@homestar9
Copy link

I've been playing around with this code and Java's softcache is really amazing! I was thinking of writing another method to your main component which would query all the cached items so I could get a "bird's eye" view of everything that is currently cached. However, I realized that by simply dumping "variables.cache" I get a structure of all cached keys but there's no way to tell if the data still exists in the softcache without running a get() method on it. Is there another way to efficiently query the softcache to see which keys are still present in the cache without actually retrieving all of the data?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment