Skip to content
Merged
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 ) ) {
Comment thread
homestar9 marked this conversation as resolved.
Outdated
// 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, {} );
Comment thread
homestar9 marked this conversation as resolved.

// 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