Skip to content

[COLDBOX-1331] Add ability to ignore or include RC keys when caching events #607

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: development
Choose a base branch
from
101 changes: 67 additions & 34 deletions system/cache/util/EventURLFacade.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -35,36 +35,66 @@ component accessors="true" {

/**
* Build a unique hash from an incoming request context
* Note: the 'event' key is always ignored from the request collection
*
* @event A request context object
* @targetContext The targeted request context object
* @eventDictionary The event metadata containing cache annotations
*/
string function getUniqueHash( required event ){
var rcTarget = arguments.event
.getCollection()
.filter( function( key, value ){
// Remove event, not needed for hashing purposes
return ( key != "event" );
} );

// systemOutput( "=====> uniquehash-rcTarget: #variables.jTreeMap.init( rcTarget ).toString()#", true );
// systemOutput( "=====> uniquehash-rcTargetHash: #hash( variables.jTreeMap.init( rcTarget ).toString() )#", true );

var targetMixer = {
// Get the original incoming context hash
"incomingHash" : hash( variables.jTreeMap.init( rcTarget ).toString() ),
// Multi-Host support
"cgihost" : buildAppLink()
};

// Incorporate Routed Structs
structAppend(
targetMixer,
arguments.event.getRoutedStruct(),
true
);

// Return unique identifier
return hash( targetmixer.toString() );
string function getUniqueHash(
required event,
required struct eventDictionary
){

// Assign the RC struct and filter out the "event" key, which is not needed for cache keys
var rcTarget = arguments.event.getCollection().filter( ( key, value ) => {
return key != "event";
} );

// Apply cache key filtering based on annotations
// We apply them in the following order:
// 1. Custom filter closure (if provided)
// 2. Include specific keys (if `cacheInclude` is not "*")
// 3. Exclude specific keys (if `cacheExclude` is provided and not empty)

// If cacheFilter isn't a simple value, we assume it's a closure and call it
if ( !isSimpleValue( arguments.eventDictionary.cacheFilter ) ) {
rcTarget = arguments.eventDictionary.cacheFilter( rcTarget );
}

// Cache Includes
// only process if cacheInclude isn't set to "*"
if ( arguments.eventDictionary.cacheInclude != "*" ) {
// Whitelist specific keys
var includeKeys = arguments.eventDictionary.cacheInclude.listToArray();
rcTarget = rcTarget.filter( ( key, value ) => {
return includeKeys.findNoCase( key ) > 0;
});
}

// Cache Excludes
if ( len( arguments.eventDictionary?.cacheExclude ) ) {
// Blacklist specific keys
var excludeKeys = arguments.eventDictionary.cacheExclude.listToArray();
rcTarget = rcTarget.filter( ( key, value ) => {
return excludeKeys.findNoCase( key ) == 0;
});
}

var targetMixer = {
// Get the original incoming context hash
"incomingHash" : hash( variables.jTreeMap.init( rcTarget ).toString() ),
// Multi-Host support
"cgihost" : buildAppLink()
};

// Incorporate Routed Structs
targetMixer.append( arguments.event.getRoutedStruct(), true );



// Return unique identifier
return hash( targetmixer.toString() );
}

/**
Expand Down Expand Up @@ -101,16 +131,19 @@ component accessors="true" {
/**
* Build an event key according to passed in params
*
* @keySuffix The key suffix used in the cache key
* @targetEvent The targeted ColdBox event executed
* @targetContext The targeted request context object
* @eventDictionary The event metadata containing cache annotations
*/
string function buildEventKey(
required keySuffix,
required targetEvent,
required targetContext
){
return buildBasicCacheKey( argumentCollection = arguments ) & getUniqueHash( arguments.targetContext );
string function buildEventKey(
required targetEvent,
required targetContext,
required struct eventDictionary
){
return buildBasicCacheKey(
keySuffix = arguments.eventDictionary.suffix,
targetEvent = arguments.targetEvent
) & getUniqueHash( arguments.targetContext, arguments.eventDictionary );
}

/**
Expand Down
51 changes: 42 additions & 9 deletions system/web/services/HandlerService.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,11 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" {
structAppend( eventCachingData, eventDictionaryEntry, true );

// Create the Cache Key to save
eventCachingData.cacheKey = oEventURLFacade.buildEventKey(
keySuffix = eventDictionaryEntry.suffix,
targetEvent = arguments.ehBean.getFullEvent(),
targetContext = oRequestContext
);
eventCachingData.cacheKey = oEventURLFacade.buildEventKey(
targetEvent = arguments.ehBean.getFullEvent(),
targetContext = oRequestContext,
eventDictionary = eventDictionaryEntry
);

// Event is cacheable and we need to flag it so the Renderer caches it
oRequestContext.setEventCacheableEntry( eventCachingData );
Expand Down Expand Up @@ -654,7 +654,10 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" {
"lastAccessTimeout" : "",
"cacheKey" : "",
"suffix" : "",
"provider" : "template"
"provider" : "template",
"cacheInclude" : "*",
"cacheExclude" : "",
"cacheFilter" : ""
};
}

Expand Down Expand Up @@ -689,17 +692,47 @@ component extends="coldbox.system.web.services.BaseService" accessors="true" {
""
);
mdEntry.provider = arguments.ehBean.getActionMetadata( "cacheProvider", "template" );
mdEntry.cacheInclude = arguments.ehBean.getActionMetadata( "cacheInclude", "*" );
mdEntry.cacheExclude = arguments.ehBean.getActionMetadata( "cacheExclude", "" );
mdEntry.cacheFilter = arguments.ehBean.getActionMetadata( "cacheFilter", "" );

// Handler Event Cache Key Suffix, this is global to the event
if (
isClosure( arguments.oEventHandler.EVENT_CACHE_SUFFIX ) || isCustomFunction(
arguments.oEventHandler.EVENT_CACHE_SUFFIX
)
isClosure( arguments.oEventHandler.EVENT_CACHE_SUFFIX ) ||
isCustomFunction( arguments.oEventHandler.EVENT_CACHE_SUFFIX )
) {
mdEntry.suffix = oEventHandler.EVENT_CACHE_SUFFIX( arguments.ehBean );
} else {
mdEntry.suffix = arguments.oEventHandler.EVENT_CACHE_SUFFIX;
}

// if the cacheFilter has a length and is a method, then we need to verify and store the resulting closure
if ( len( mdEntry.cacheFilter ) ) {

// if the method doesn't exist, then throw an exception
if ( !arguments.oEventHandler._actionExists( mdEntry.cacheFilter ) ) {
throw(
message = "CacheFilter expected a private method '#mdEntry.cacheFilter#'",
type = "HandlerInvalidCacheFilterException",
detail = "CacheFilter method '#mdEntry.cacheFilter#' does not exist in handler '#getMetaData( oEventHandler ).name#'. Please verify your cacheFilter annotation."
);
}

mdEntry.cacheFilter = arguments.oEventHandler._privateInvoker( mdEntry.cacheFilter, {} );

// if the cacheFilter isn't a closure, throw an exception
// We check for isClosure and isCustomFunction for ACF/Lucee compatibility
if (
!isClosure( mdEntry.cacheFilter ) &&
!isCustomFunction( mdEntry.cacheFilter )
) {
throw(
message = "CacheFilter expected a closure.",
type = "HandlerInvalidCacheFilterException",
detail = "Please verify your cacheFilter annotation in handler '#getMetaData( oEventHandler ).name# to ensure it returns a closure."
);
}
}
}
// end cache metadata is true

Expand Down
6 changes: 3 additions & 3 deletions system/web/services/RequestService.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -153,12 +153,12 @@ component extends="coldbox.system.web.services.BaseService" {
}

// Incorporate metadata about event
eventCache.append( eventDictionary, true );
eventCache.append( eventDictionary, true );
// Build the event cache key according to incoming request
eventCache[ "cacheKey" ] = oEventURLFacade.buildEventKey(
keySuffix = eventDictionary.suffix,
targetEvent = currentEvent,
targetContext = arguments.context
targetContext = arguments.context,
eventDictionary = eventDictionary
);

// Check for Event Cache Purge
Expand Down
Loading
Loading