diff --git a/framework/default/ortoo-core/default/classes/fflib-extension/caching/ICacheAdaptor.cls b/framework/default/ortoo-core/default/classes/fflib-extension/caching/ICacheAdaptor.cls index 75d4a510d9b..a145dcd70c1 100644 --- a/framework/default/ortoo-core/default/classes/fflib-extension/caching/ICacheAdaptor.cls +++ b/framework/default/ortoo-core/default/classes/fflib-extension/caching/ICacheAdaptor.cls @@ -15,4 +15,5 @@ public interface ICacheAdaptor void put( String key, Object value, Integer lifespan ); Boolean contains( String key ); void remove( String key ); + Set getKeys(); } \ No newline at end of file diff --git a/framework/default/ortoo-core/default/classes/fflib-extension/caching/NullCache.cls b/framework/default/ortoo-core/default/classes/fflib-extension/caching/NullCache.cls index c2bf10eb631..7e3d189cc44 100644 --- a/framework/default/ortoo-core/default/classes/fflib-extension/caching/NullCache.cls +++ b/framework/default/ortoo-core/default/classes/fflib-extension/caching/NullCache.cls @@ -22,6 +22,11 @@ public inherited sharing class NullCache implements ICacheAdaptor return null; } + public Set getKeys() + { + return new Set(); + } + public void put( String key, Object value, Integer lifespan ) // NOPMD: Intentionally left empty, as this should do nothing in a NullCache { } diff --git a/framework/default/ortoo-core/default/classes/fflib-extension/caching/ObjectCache.cls b/framework/default/ortoo-core/default/classes/fflib-extension/caching/ObjectCache.cls index d09e2dda259..9097067d8d3 100644 --- a/framework/default/ortoo-core/default/classes/fflib-extension/caching/ObjectCache.cls +++ b/framework/default/ortoo-core/default/classes/fflib-extension/caching/ObjectCache.cls @@ -8,7 +8,7 @@ * Note: The lifespan of a given object is reset whenever any object in that object's list is written. * That is, either the whole of the list is aged out, or none of it is. */ -public with sharing class ObjectCache +public inherited sharing class ObjectCache { public class InvalidIdentifierException extends Exceptions.DeveloperException {} @@ -119,16 +119,9 @@ public with sharing class ObjectCache CacheRetrieval values = new CacheRetrieval(); - Map cachedObjects = (Map)cacheWrapper.get( key ); - - if ( cachedObjects == null ) - { - return values.setCacheMisses( ids ); - } - for ( String thisId : ids ) { - Object thisValue = cachedObjects.get( thisId ); + Object thisValue = cacheWrapper.get( buildFullKey( key, thisId ) ); if ( thisValue != null ) { values.addCacheHit( thisId, thisValue ); @@ -212,6 +205,9 @@ public with sharing class ObjectCache } // TODO: implement put( String key, IdGetter idGetter, Object objectToStore ) + // TODO: implement put( String key, String idField, Sobject sobjectToStore ) + // TODO: implement put( String key, Sobject sobjectToStore ) + /** * Put the given Objects into the cache, using the given base key combined with the value that is returned from * each object when the IdGetter is called against it @@ -227,22 +223,15 @@ public with sharing class ObjectCache Contract.requires( idGetter != null, 'put called with a null idGetter' ); Contract.requires( objects != null, 'put called with a null objects' ); - Map cachedObjects = (Map)cacheWrapper.get( key ); - - if ( cachedObjects == null ) - { - cachedObjects = new Map(); - } - for ( Object thisObject : objects ) { Contract.assert( thisObject != null, 'put called with a null entry in objects' ); String thisId = idGetter.getIdFor( thisObject ); Contract.assert( thisId != null, 'put called with an object that has a null Id: ' + thisObject ); - cachedObjects.put( thisId, thisObject ); + + cacheWrapper.put( buildFullKey( key, thisId ), thisObject, CACHE_LIFESPAN_SECONDS ); } - cacheWrapper.put( key, cachedObjects, CACHE_LIFESPAN_SECONDS ); return this; } @@ -253,10 +242,19 @@ public with sharing class ObjectCache * @param String The base key to remove the objects from. * @return ObjectCache Itself, allowing for a fluent interface */ - public ObjectCache remove( String key ) + public ObjectCache remove( String keyToRemove ) { - Contract.requires( key != null, 'remove called with a null key' ); - cacheWrapper.remove( key ); + Contract.requires( keyToRemove != null, 'remove called with a null keyToRemove' ); + + Set allExistingKeys = cacheWrapper.getKeys(); + + for ( String thisExistingKey : allExistingKeys ) + { + if ( isForKey( thisExistingKey, keyToRemove ) ) + { + cacheWrapper.remove( thisExistingKey ); + } + } return this; } @@ -285,23 +283,34 @@ public with sharing class ObjectCache Contract.requires( key != null, 'remove called with a null key' ); Contract.requires( ids != null, 'remove called with a null ids' ); - Map cachedObjects = (Map)cacheWrapper.get( key ); - - if ( cachedObjects == null ) + // This is two loops because we want to check all the ids before we remove anything + for ( String thisId : ids ) { - return this; + Contract.assert( thisId != null, 'remove called with a null entry in ids' ); } for ( String thisId : ids ) { - Contract.assert( thisId != null, 'remove called with a null entry in ids' ); - - cachedObjects.remove( thisId ); + cacheWrapper.remove( buildFullKey( key, thisId ) ); } - cacheWrapper.put( key, cachedObjects, CACHE_LIFESPAN_SECONDS ); return this; } + private String buildFullKey( String key, String id ) + { + return buildKeyPrefix( key ) + id; + } + + private Boolean isForKey( String fullKey, String key ) + { + return fullKey.startsWith( buildKeyPrefix( key ) ); + } + + private String buildKeyPrefix( String key ) + { + return key + 'xXXx'; + } + private class SobjectIdGetter implements IdGetter { String idField; diff --git a/framework/default/ortoo-core/default/classes/fflib-extension/caching/OrgCache.cls b/framework/default/ortoo-core/default/classes/fflib-extension/caching/OrgCache.cls index a8445d987a7..852edb80194 100644 --- a/framework/default/ortoo-core/default/classes/fflib-extension/caching/OrgCache.cls +++ b/framework/default/ortoo-core/default/classes/fflib-extension/caching/OrgCache.cls @@ -70,6 +70,24 @@ public inherited sharing class OrgCache implements ICacheAdaptor return Cache.Org.get( createFullyQualifiedKey( key ) ); } + /** + * Retrieve all the keys that are currently stored in the cache. + * + * If the user does not have access to the cache, will throw an AccessViolationException. + * + * @return Set The keys for all cached objects + */ + public Set getKeys() + { + if ( ! hasAccessToCache ) + { + throw new AccessViolationException( Label.ortoo_core_org_cache_access_violation ) + .setErrorCode( FrameworkErrorCodes.CACHE_ACCESS_VIOLATION ) + .addContext( 'method', 'getKeys' ); + } + return Cache.Org.getKeys(); + } + /** * Put the stated value into the stated key for the specified duration (in seconds) * @@ -122,7 +140,7 @@ public inherited sharing class OrgCache implements ICacheAdaptor } /** - * Instucts the cache to remove the object at the given key + * Instructs the cache to remove the object at the given key * * If the user does not have access to the cache, will throw an AccessViolationException. * @@ -143,7 +161,7 @@ public inherited sharing class OrgCache implements ICacheAdaptor } /** - * Instucts the cache to remove all objects from the cache. + * Instructs the cache to remove all objects from the cache. * * Is useful for clearing the cache out completely when the majority of entries would otherwise be dirty. * diff --git a/framework/default/ortoo-core/default/classes/fflib-extension/caching/SessionCache.cls b/framework/default/ortoo-core/default/classes/fflib-extension/caching/SessionCache.cls index 90c8441d770..9b3b53a9768 100644 --- a/framework/default/ortoo-core/default/classes/fflib-extension/caching/SessionCache.cls +++ b/framework/default/ortoo-core/default/classes/fflib-extension/caching/SessionCache.cls @@ -39,6 +39,16 @@ public inherited sharing class SessionCache implements ICacheAdaptor return Cache.Session.get( key ); } + /** + * Retrieve all the keys that are currently stored in the cache. + * + * @return Set The keys for all cached objects + */ + public Set getKeys() + { + return Cache.Session.getKeys(); + } + /** * Put the stated value into the stated key for the specified duration (in seconds) * @@ -72,7 +82,7 @@ public inherited sharing class SessionCache implements ICacheAdaptor } /** - * Instucts the cache to remove the object at the given key + * Instructs the cache to remove the object at the given key * * @param String The key to remove */ diff --git a/framework/default/ortoo-core/default/classes/fflib-extension/caching/tests/ObjectCacheTest.cls b/framework/default/ortoo-core/default/classes/fflib-extension/caching/tests/ObjectCacheTest.cls index 1d0d53bd03d..14874a7c592 100644 --- a/framework/default/ortoo-core/default/classes/fflib-extension/caching/tests/ObjectCacheTest.cls +++ b/framework/default/ortoo-core/default/classes/fflib-extension/caching/tests/ObjectCacheTest.cls @@ -546,6 +546,9 @@ private without sharing class ObjectCacheTest System.assert( true, 'remove, when given a key and Id combination that does not exist, will not throw an exception' ); } +// TODO: remove called with a null / empty id will throw an exception +// TODO: remove called with a null / empty id will not remove other things + public class StringIdGetter implements ObjectCache.IdGetter { public String getIdFor( Object objectToGetIdFor )