From 408c0964a28add44451ea1309519f9868983dafb Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 19 Dec 2024 16:38:58 -0500 Subject: [PATCH] CACHEBOX-87 #resolve BoxLang Providers --- .../providers/BoxLangColdBoxProvider.cfc | 170 ++++++++ system/cache/providers/BoxLangProvider.cfc | 392 ++++++++++++++++++ .../providers/boxlang-lib/BoxLangStats.cfc | 100 +++++ .../cache/providers/CFColdBoxProviderTest.cfc | 1 - .../specs/cache/providers/CFProviderTest.cfc | 6 +- .../providers/CacheBoxWithDiskSoreTest.cfc | 2 +- .../providers/CacheBoxWithJDBCSoreTest.cfc | 2 +- .../providers/LuceeColdBoxProviderTest.cfc | 3 +- .../cache/providers/LuceeProviderTest.cfc | 6 +- 9 files changed, 671 insertions(+), 11 deletions(-) create mode 100644 system/cache/providers/BoxLangColdBoxProvider.cfc create mode 100644 system/cache/providers/BoxLangProvider.cfc create mode 100644 system/cache/providers/boxlang-lib/BoxLangStats.cfc diff --git a/system/cache/providers/BoxLangColdBoxProvider.cfc b/system/cache/providers/BoxLangColdBoxProvider.cfc new file mode 100644 index 000000000..ac4680abe --- /dev/null +++ b/system/cache/providers/BoxLangColdBoxProvider.cfc @@ -0,0 +1,170 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * + * This CacheBox provider communicates with the built in caches in the Lucee Engine for ColdBox Apps + * + * @author Luis Majano + */ +component + accessors ="true" + serializable="false" + implements ="coldbox.system.cache.providers.IColdBoxProvider" + extends ="coldbox.system.cache.providers.LuceeProvider" +{ + + // Cache Prefixes + this.VIEW_CACHEKEY_PREFIX = "boxlang_view-"; + this.EVENT_CACHEKEY_PREFIX = "boxlang_event-"; + + /** + * Constructor + */ + BoxLangColdBoxProvider function init(){ + super.init(); + + // URL Facade Utility + variables.eventURLFacade = new coldbox.system.cache.util.EventURLFacade( this ); + + return this; + } + + /** + * Get the cached view key prefix which is necessary for view caching + */ + function getViewCacheKeyPrefix(){ + return this.VIEW_CACHEKEY_PREFIX; + }; + + /** + * Get the event cache key prefix which is necessary for event caching + */ + function getEventCacheKeyPrefix(){ + return this.EVENT_CACHEKEY_PREFIX; + } + + /** + * Get the coldbox application reference as coldbox.system.web.Controller + * + * @return coldbox.system.web.Controller + */ + function getColdbox(){ + return variables.coldbox; + } + + /** + * Set the ColdBox linkage into the provider + * + * @coldbox The ColdBox controller + * @coldbox.doc_generic coldbox.system.web.Controller + * + * @return IColdboxApplicationCache + */ + function setColdBox( required coldbox ){ + variables.coldbox = arguments.coldbox; + return this; + } + + /** + * Get the event caching URL facade utility that determines event caching + * + * @return coldbox.system.cache.util.EventURLFacade + */ + function getEventURLFacade(){ + return variables.eventURLFacade; + } + + /** + * Clears all events from the cache. + * + * @async If implemented, determines async or sync clearing. + * + * @return IColdboxApplicationCache + */ + function clearAllEvents( boolean async = false ){ + var threadName = "clearAllEvents_#replace( randomUUID(), "-", "", "all" )#"; + + // Async? IF so, do checks + if ( arguments.async AND NOT variables.utility.inThread() ) { + thread name="#threadName#" { + variables.elementCleaner.clearAllEvents(); + } + } else { + variables.elementCleaner.clearAllEvents(); + } + return this; + } + + /** + * Clears all the event permutations from the cache according to snippet and querystring. Be careful when using incomplete event name with query strings as partial event names are not guaranteed to match with query string permutations + * + * @eventSnippet The event snippet to clear on. Can be partial or full + * @queryString If passed in, it will create a unique hash out of it. For purging purposes + * + * @return IColdboxApplicationCache + */ + function clearEvent( required eventSnippet, queryString = "" ){ + variables.elementCleaner.clearEvent( arguments.eventsnippet, arguments.queryString ); + return this; + } + + /** + * Clears all views from the cache. + * + * @async Run command asynchronously or not + * + * @return IColdboxApplicationCache + */ + function clearAllViews( boolean async = false ){ + var threadName = "clearAllViews_#replace( randomUUID(), "-", "", "all" )#"; + + // Async? IF so, do checks + if ( arguments.async AND NOT variables.utility.inThread() ) { + thread name="#threadName#" { + variables.elementCleaner.clearAllViews(); + } + } else { + variables.elementCleaner.clearAllViews(); + } + return this; + } + + /** + * Clears all the event permutations from the cache according to the list of snippets and querystrings. Be careful when using incomplete event name with query strings as partial event names are not guaranteed to match with query string permutations + * + * @eventSnippet The comma-delimited list event snippet to clear on. Can be partial or full + * @queryString The comma-delimited list of queryStrings passed in. If passed in, it will create a unique hash out of it. For purging purposes. If passed in the list length must be equal to the list length of the event snippets passed in + * + * @return IColdboxApplicationCache + */ + function clearEventMulti( required eventsnippets, queryString = "" ){ + variables.elementCleaner.clearEventMulti( arguments.eventsnippets, arguments.queryString ); + return this; + } + + /** + * Clears all view name permutations from the cache according to the view name + * + * @viewSnippet The view name snippet to purge from the cache + * + * @return IColdboxApplicationCache + */ + function clearView( required viewSnippet ){ + variables.elementCleaner.clearView( arguments.viewSnippet ); + return this; + } + + /** + * Clears all view name permutations from the cache according to the view name. + * + * @viewSnippets The comma-delimited list or array of view snippet to clear on. Can be partial or full + * + * @return IColdboxApplicationCache + */ + function clearViewMulti( required viewSnippets ){ + variables.elementCleaner.clearView( arguments.viewsnippets ); + return this; + } + +} diff --git a/system/cache/providers/BoxLangProvider.cfc b/system/cache/providers/BoxLangProvider.cfc new file mode 100644 index 000000000..f79b4dd25 --- /dev/null +++ b/system/cache/providers/BoxLangProvider.cfc @@ -0,0 +1,392 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * + * This CacheBox provider communicates with the built in caches in the BoxLang Runtime + * + * @author Luis Majano + */ +component + accessors ="true" + serializable="false" + implements ="coldbox.system.cache.providers.ICacheProvider" + extends ="coldbox.system.cache.AbstractCacheBoxProvider" +{ + + /** + * The global element cleaner utility object + */ + property name="elementCleaner"; + + // Provider Property Defaults STATIC + variables.DEFAULTS = { cacheName : "default" }; + + /** + * Constructor + */ + function init(){ + super.init(); + + // Element Cleaner Helper + variables.elementCleaner = new coldbox.system.cache.util.ElementCleaner( this ); + + return this; + } + + /** + * configure the cache for operation + * + * @return BoxLangProvider + */ + function configure(){ + lock name="BoxLangProvider.config.#variables.cacheID#" type="exclusive" throwontimeout="true" timeout="30" { + // Prepare the logger + variables.logger = getCacheFactory().getLogBox().getLogger( this ); + + if ( variables.logger.canDebug() ) { + variables.logger.debug( + "Starting up BoxLangProvider Cache: #getName()# with configuration: #variables.configuration.toString()#" + ); + } + + // Validate the configuration + validateConfiguration(); + + // enabled cache + variables.enabled = true; + variables.reportingEnabled = true; + + if ( variables.logger.canDebug() ) { + variables.logger.debug( "Cache #getName()# started up successfully" ); + } + } + + return this; + } + + /** + * Shutdown command issued when CacheBox is going through shutdown phase + * + * @return BoxLangProvider + */ + function shutdown(){ + // nothing to shutdown, the runtime takes care of it. + if ( variables.logger.canDebug() ) { + variables.logger.debug( "BoxLangProvider Cache: #getName()# has been shutdown." ); + } + return this; + } + + /** + * Get the cache statistics object as coldbox.system.cache.util.IStats + * + * @return coldbox.system.cache.util.IStats + */ + function getStats(){ + return new "coldbox.system.cache.providers.boxlang-lib.BoxLangStats"( this ); + } + + /** + * Clear the cache statistics + * THIS FUNCTION IS NOT IMPLEMENTED IN THIS PROVIDER + * + * @return ICacheProvider + */ + function clearStatistics(){ + return cache( getConfiguration().cacheName ).clearStats(); + } + + /** + * If the cache provider implements it, this returns the cache's object store. + * + * @return coldbox.system.cache.store.IObjectStore or any depending on the cache implementation + */ + function getObjectStore(){ + return cache( getConfiguration().cacheName ).getObjectStore(); + } + + /** + * Get a structure of all the keys in the cache with their appropriate metadata structures. This is used to build the reporting.[keyX->[metadataStructure]] + */ + struct function getStoreMetadataReport(){ + return cache( getConfiguration().cacheName ).getStoreMetadataReport(); + } + + /** + * Get a key lookup structure where cachebox can build the report on. Ex: [timeout=timeout,lastAccessTimeout=idleTimeout]. It is a way for the visualizer to construct the columns correctly on the reports + */ + struct function getStoreMetadataKeyMap(){ + return { + "cacheName", "cacheName", + "hits", "hits", + "timeout", "timeout", + "lastAccessTimeout", "lastAccessTimeout", + "created", "created", + "lastAccessed", "lastAccessed", + "metadata", "metadata", + "key", "key", + "isEternal", "isEternal" + }; + } + + /** + * Returns a list of all elements in the cache, whether or not they are expired + */ + array function getKeys(){ + return cache( getConfiguration().cacheName ).getKeys(); + } + + /** + * Get a cache objects metadata about its performance. This value is a structure of name-value pairs of metadata. + * + * @objectKey The key to retrieve + */ + struct function getCachedObjectMetadata( required objectKey ){ + return cache( getConfiguration().cacheName ).getCachedObjectMetadata( arguments.objectKey ); + } + + /** + * Get an object from the cache + * + * @objectKey The key to retrieve + */ + function get( required objectKey ){ + return cache( getConfiguration().cacheName ).get( arguments.objectKey ); + } + + /** + * get an item silently from cache, no stats advised: Stats not available on lucee + * + * @objectKey The key to retrieve + */ + function getQuiet( required objectKey ){ + return cache( getConfiguration().cacheName ).getQuiet( arguments.objectKey ); + } + + /** + * Has the object key expired in the cache: NOT IMPLEMENTED IN THIS CACHE + * + * @objectKey The key to retrieve + */ + boolean function isExpired( required objectKey ){ + return false; + } + + /** + * Check if an object is in cache, if not found it records a miss. + * + * @objectKey The key to retrieve + */ + boolean function lookup( required objectKey ){ + return cache( getConfiguration().cacheName ).lookup( arguments.objectKey ); + } + + /** + * Check if an object is in cache, no stats updated or listeners + * + * @objectKey The key to retrieve + */ + boolean function lookupQuiet( required objectKey ){ + return cache( getConfiguration().cacheName ).lookupQuiet( arguments.objectKey ); + } + + /** + * Tries to get an object from the cache, if not found, it calls the 'produce' closure to produce the data and cache it + * + * @objectKey The object cache key + * @produce The producer closure/lambda + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return The cached or produced data/object + */ + any function getOrSet( + required any objectKey, + required any produce, + any timeout = "0", + any lastAccessTimeout = "0", + any extra = {} + ){ + return cache( getConfiguration().cacheName ) + .getOrSet( + arguments.objectKey, + arguments.produce, + arguments.timeout, + arguments.lastAccessTimeout, + arguments.extra + ); + } + + /** + * Sets an object in the cache and returns an instance of itself + * + * @objectKey The object cache key + * @object The object to cache + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return ICacheProvider + */ + function set( + required objectKey, + required object, + timeout = 0, + lastAccessTimeout = 0, + struct extra + ){ + cache( getConfiguration().cacheName ).set( + arguments.objectKey, + arguments.object, + arguments.timeout, + arguments.lastAccessTimeout, + arguments.extra + ); + + // ColdBox events + var iData = { + cache : this, + cacheObject : arguments.object, + cacheObjectKey : arguments.objectKey, + cacheObjectTimeout : arguments.timeout, + cacheObjectLastAccessTimeout : arguments.lastAccessTimeout + }; + getEventManager().announce( "afterCacheElementInsert", iData ); + + return this; + } + + /** + * Sets an object in the cache with no event calls and returns an instance of itself + * + * @objectKey The object cache key + * @object The object to cache + * @timeout The timeout to use on the object (if any, provider specific) + * @lastAccessTimeout The idle timeout to use on the object (if any, provider specific) + * @extra A map of name-value pairs to use as extra arguments to pass to a providers set operation + * + * @return ICacheProvider + */ + function setQuiet( + required objectKey, + required object, + timeout = 0, + lastAccessTimeout = 0, + struct extra + ){ + cache( getConfiguration().cacheName ).set( + arguments.objectKey, + arguments.object, + arguments.timeout, + arguments.lastAccessTimeout, + arguments.extra + ); + + return this; + } + + /** + * Get the number of elements in the cache + */ + numeric function getSize(){ + return cache( getConfiguration().cacheName ).getSize(); + } + + /** + * Send a reap or flush command to the cache: Not implemented by this provider + * + * @return ICacheProvider + */ + function reap(){ + cache( getConfiguration().cacheName ).reap(); + return this; + } + + /** + * Clear all the cache elements from the cache + * + * @return ICacheProvider + */ + function clearAll(){ + cache( getConfiguration().cacheName ).clearAll(); + // notify listeners + getEventManager().announce( "afterCacheClearAll", { cache : this } ); + return this; + } + + /** + * Clears an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore + * + * @objectKey The object cache key + */ + boolean function clear( required objectKey ){ + var results = cache( getConfiguration().cacheName ).clear( arguments.objectKey ); + + // ColdBox events + getEventManager().announce( + "afterCacheElementRemoved", + { cache : this, cacheObjectKey : arguments.objectKey } + ); + + return results; + } + + /** + * Clears an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore without doing statistics or updating listeners + * + * @objectKey The object cache key + */ + boolean function clearQuiet( required objectKey ){ + // normal clear, not implemented by lucee + return cache( getConfiguration().cacheName ).clearQuiet( arguments.objectKey ); + } + + /** + * Expire all the elements in the cache (if supported by the provider) + * THIS FUNCTION IS NOT IMPLEMENTED IN THIS PROVIDER + * + * @return ICacheProvider + */ + function expireAll(){ + return this; + } + + /** + * Expires an object from the cache by using its cache key. Returns false if object was not removed or did not exist anymore (if supported by the provider) + * THIS FUNCTION IS NOT IMPLEMENTED IN THIS PROVIDER + * + * @objectKey The object cache key + * + * @return ICacheProvider + */ + function expireObject( required objectKey ){ + return this; + } + + /** + * Get the underlying BoxLang cache object + */ + function getCache(){ + return getCache( getConfiguration().cacheName ); + } + + /******************************** PRIVATE ********************************/ + + /** + * Validate the incoming configuration and make necessary defaults + * + * @return BoxLangProvider + **/ + private function validateConfiguration(){ + // Add in settings not discovered + structAppend( + variables.configuration, + variables.DEFAULTS, + false + ); + return this; + } + +} diff --git a/system/cache/providers/boxlang-lib/BoxLangStats.cfc b/system/cache/providers/boxlang-lib/BoxLangStats.cfc new file mode 100644 index 000000000..84b03a08c --- /dev/null +++ b/system/cache/providers/boxlang-lib/BoxLangStats.cfc @@ -0,0 +1,100 @@ +/** + * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * + * Boxlang stats implementation + * + * @author Luis Majano + */ +component implements="coldbox.system.cache.util.IStats" accessors="true" { + + property name="cacheProvider" serializable="false"; + + /** + * Constructor + * + * @cacheProvider The associated cache manager/provider of type: coldbox.system.cache.providers.ICacheProvider + * @cacheProvider.doc_generic coldbox.system.cache.providers.ICacheProvider + */ + function init( required cacheProvider ){ + setCacheProvider( arguments.cacheProvider ); + return this; + } + + /** + * Get the cache's performance ratio + */ + numeric function getCachePerformanceRatio(){ + return getCacheProvider().getCache().getStats().hitRate(); + } + + /** + * Get the associated cache's live object count + */ + numeric function getObjectCount(){ + return getCacheProvider().getSize(); + } + + /** + * Clear the stats + * + * @return IStats + */ + function clearStatistics(){ + return getCacheProvider().clearStatistics(); + return this; + } + + /** + * Get the total cache's garbage collections + */ + numeric function getGarbageCollections(){ + return getCacheProvider().getCache().getStats().garbageCollections(); + } + + /** + * Get the total cache's eviction count + */ + numeric function getEvictionCount(){ + return getCacheProvider().getCache().getStats().evictionCount(); + } + + /** + * Get the total cache's hits + */ + numeric function getHits(){ + return getCacheProvider().getCache().getStats().hits(); + } + + /** + * Get the total cache's misses + */ + numeric function getMisses(){ + return getCacheProvider().getCache().getStats().misses(); + } + + /** + * Get the date/time of the last reap the cache did + * + * @return date/time or empty + */ + function getLastReapDatetime(){ + return getCacheProvider().getCache().getStats().lastReapDatetime(); + } + + /** + * Get the total cache's reap count + */ + numeric function getReapCount(){ + return getCacheProvider().getCache().getStats().reapCount(); + } + + /** + * When the cache was started + */ + function getStarted(){ + return getCacheProvider().getCache().getStats().started(); + } + +} diff --git a/tests/specs/cache/providers/CFColdBoxProviderTest.cfc b/tests/specs/cache/providers/CFColdBoxProviderTest.cfc index 0b4da066d..e8f7eb80c 100755 --- a/tests/specs/cache/providers/CFColdBoxProviderTest.cfc +++ b/tests/specs/cache/providers/CFColdBoxProviderTest.cfc @@ -1,5 +1,4 @@ component - name ="cacheTest" extends="CFProviderTest" output ="false" skip ="isAdobe" diff --git a/tests/specs/cache/providers/CFProviderTest.cfc b/tests/specs/cache/providers/CFProviderTest.cfc index dce46caff..7def0a597 100755 --- a/tests/specs/cache/providers/CFProviderTest.cfc +++ b/tests/specs/cache/providers/CFProviderTest.cfc @@ -1,9 +1,9 @@ -component extends="tests.resources.BaseIntegrationTest" skip="isAdobe" { +component extends="tests.resources.BaseIntegrationTest" skip="notAdobe" { this.loadColdBox = false; - boolean function isAdobe(){ - return server.keyExists( "lucee" ); + boolean function notAdobe(){ + return isLucee() || isBoxLang(); } function setup(){ diff --git a/tests/specs/cache/providers/CacheBoxWithDiskSoreTest.cfc b/tests/specs/cache/providers/CacheBoxWithDiskSoreTest.cfc index 3aeebbcc2..c81c35d2e 100755 --- a/tests/specs/cache/providers/CacheBoxWithDiskSoreTest.cfc +++ b/tests/specs/cache/providers/CacheBoxWithDiskSoreTest.cfc @@ -1,4 +1,4 @@ -component name="cacheTest" extends="CacheBoxProviderTest" { +component extends="CacheBoxProviderTest" { function setup(){ super.setup(); diff --git a/tests/specs/cache/providers/CacheBoxWithJDBCSoreTest.cfc b/tests/specs/cache/providers/CacheBoxWithJDBCSoreTest.cfc index 4850403ac..2b799081f 100755 --- a/tests/specs/cache/providers/CacheBoxWithJDBCSoreTest.cfc +++ b/tests/specs/cache/providers/CacheBoxWithJDBCSoreTest.cfc @@ -1,4 +1,4 @@ -component name="cacheTest" extends="CacheBoxProviderTest" { +component extends="CacheBoxProviderTest" { function setup(){ super.setup(); diff --git a/tests/specs/cache/providers/LuceeColdBoxProviderTest.cfc b/tests/specs/cache/providers/LuceeColdBoxProviderTest.cfc index 6d265f950..2acfc1ab6 100755 --- a/tests/specs/cache/providers/LuceeColdBoxProviderTest.cfc +++ b/tests/specs/cache/providers/LuceeColdBoxProviderTest.cfc @@ -1,8 +1,7 @@ component - name ="cacheTest" extends="LuceeProviderTest" output ="false" - skip ="isLucee" + skip ="notLucee" { function setup(){ diff --git a/tests/specs/cache/providers/LuceeProviderTest.cfc b/tests/specs/cache/providers/LuceeProviderTest.cfc index 466864ba6..7290e28ef 100755 --- a/tests/specs/cache/providers/LuceeProviderTest.cfc +++ b/tests/specs/cache/providers/LuceeProviderTest.cfc @@ -1,9 +1,9 @@ -component extends="tests.resources.BaseIntegrationTest" skip="isLucee" { +component extends="tests.resources.BaseIntegrationTest" skip="notLucee" { this.loadColdBox = false; - boolean function isLucee(){ - return listFindNoCase( "Lucee", server.coldfusion.productname ) ? false : true; + boolean function notLucee(){ + return isAdobe() || isBoxLang(); } function setup(){