Created
March 25, 2014 11:22
-
-
Save bennadel/9759813 to your computer and use it in GitHub Desktop.
Creating A Simple ColdFusion Cache With Java Soft-References
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- 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> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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?