diff --git a/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/Cache.java b/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/Cache.java index a13a7d1d560..15f30919812 100644 --- a/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/Cache.java +++ b/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/Cache.java @@ -48,6 +48,7 @@ public Cache(final CacheConfig config) { config.getMaximumSize().map(cacheBuilder::maximumSize); config.getExpireAfterAccess().map(ms -> cacheBuilder.expireAfterAccess(ms, TimeUnit.MILLISECONDS)); + config.getExpireAfterWrite().map(ms -> cacheBuilder.expireAfterWrite(ms, TimeUnit.MILLISECONDS)); this.store = cacheBuilder.build(); } diff --git a/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/CacheConfig.java b/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/CacheConfig.java index 98b4a4b8063..d817cae97a9 100644 --- a/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/CacheConfig.java +++ b/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/CacheConfig.java @@ -33,16 +33,19 @@ public class CacheConfig { private final Optional permissions; private final Optional maximumSize; private final Optional expireAfterAccess; + private final Optional expireAfterWrite; /** * @param permissions Any restrictions on cache operations * @param maximumSize The maximimum number of entries in the cache * @param expireAfterAccess The time in milliseconds after the entry is last accessed, that it should expire + * @param expireAfterWrite The time in milliseconds after the entry is last modified, that it should expire */ - public CacheConfig(final Optional permissions, final Optional maximumSize, final Optional expireAfterAccess) { + public CacheConfig(final Optional permissions, final Optional maximumSize, final Optional expireAfterAccess, final Optional expireAfterWrite) { this.permissions = permissions; this.maximumSize = maximumSize; this.expireAfterAccess = expireAfterAccess; + this.expireAfterWrite = expireAfterWrite; } public Optional getPermissions() { @@ -57,6 +60,10 @@ public Optional getExpireAfterAccess() { return expireAfterAccess; } + public Optional getExpireAfterWrite() { + return expireAfterWrite; + } + public static class Permissions { private final Optional putGroup; private final Optional getGroup; diff --git a/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/CacheFunctions.java b/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/CacheFunctions.java index 6633be0cd15..e99c2b58ccf 100644 --- a/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/CacheFunctions.java +++ b/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/CacheFunctions.java @@ -62,7 +62,7 @@ public class CacheFunctions extends BasicFunction { "Explicitly create a cache with a specific configuration", returns(Type.BOOLEAN, "true if the cache was created, false if the cache already exists"), FS_PARAM_CACHE_NAME, - param("config", Type.MAP, "A map with configuration for the cache. At present cache LRU and permission groups may be specified, for operations on the cache. `maximumSize` is optional and specifies the maximum number of entries. `expireAfterAccess` is optional and specifies the expiry period for infrequently accessed entries (in milliseconds). If a permission group is not specified for an operation, then permissions are not checked for that operation. Should have the format: map { \"maximumSize\": 1000, \"expireAfterAccess\": 120000, \"permissions\": map { \"put-group\": \"group1\", \"get-group\": \"group2\", \"remove-group\": \"group3\", \"clear-group\": \"group4\"} }") + param("config", Type.MAP, "A map with configuration for the cache. At present cache LRU and permission groups may be specified, for operations on the cache. `maximumSize` is optional and specifies the maximum number of entries. `expireAfterAccess` is optional and specifies the expiry period for infrequently accessed entries (in milliseconds). `expireAfterWrite` is optional and specifies the expiry period after the entry's creation, or the most recent replacement of its value (in milliseconds). If a permission group is not specified for an operation, then permissions are not checked for that operation. Should have the format: map { \"maximumSize\": 1000, \"expireAfterAccess\": 120000, \"expireAfterWrite\": 240000, \"permissions\": map { \"put-group\": \"group1\", \"get-group\": \"group2\", \"remove-group\": \"group3\", \"clear-group\": \"group4\"} }") ); private static final String FS_NAMES_NAME = "names"; @@ -277,7 +277,16 @@ private CacheConfig extractCacheConfig(final MapType configMap) throws XPathExce expireAfterAccess = Optional.empty(); } - return new CacheConfig(permissions, maximumSize, expireAfterAccess); + final Sequence expireAfterWriteSeq = configMap.get(new StringValue(this, "expireAfterWrite")); + final Optional expireAfterWrite; + if(expireAfterWriteSeq != null && expireAfterWriteSeq.getItemCount() == 1) { + final long l = expireAfterWriteSeq.itemAt(0).toJavaObject(Long.class); + expireAfterWrite = Optional.of(l); + } else { + expireAfterWrite = Optional.empty(); + } + + return new CacheConfig(permissions, maximumSize, expireAfterAccess, expireAfterWrite); } private Optional getStringValue(final String key, final AbstractMapType map) { diff --git a/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/CacheModule.java b/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/CacheModule.java index 86dcac3e1b9..856b3630026 100644 --- a/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/CacheModule.java +++ b/extensions/modules/cache/src/main/java/org/exist/xquery/modules/cache/CacheModule.java @@ -71,6 +71,7 @@ public class CacheModule extends AbstractInternalModule { private static final String PARAM_NAME_ENABLE_LAZY_CREATION = "enableLazyCreation"; private static final String PARAM_NAME_LAZY_MAXIMUM_SIZE = "lazy.maximumSize"; private static final String PARAM_NAME_LAZY_EXPIRE_AFTER_ACCESS = "lazy.expireAfterAccess"; + private static final String PARAM_NAME_LAZY_EXPIRE_AFTER_WRITE = "lazy.expireAfterWrite"; private static final String PARAM_NAME_LAZY_PUT_GROUP = "lazy.putGroup"; private static final String PARAM_NAME_LAZY_GET_GROUP = "lazy.getGroup"; private static final String PARAM_NAME_LAZY_REMOVE_GROUP = "lazy.removeGroup"; @@ -78,6 +79,7 @@ public class CacheModule extends AbstractInternalModule { private static final long DEFAULT_LAZY_MAXIMUM_SIZE = 128; // 128 items private static final long DEFAULT_LAZY_EXPIRE_AFTER_ACCESS = 1000 * 60 * 5; // 5 minutes + private static final long DEFAULT_LAZY_EXPIRE_AFTER_WRITE = 1000 * 60 * 5; // 5 minutes static final Map caches = new ConcurrentHashMap<>(); @@ -167,8 +169,18 @@ private static Optional parseParameters(final Map> } }); + final Optional expireAfterWrite = getFirstString(parameters, PARAM_NAME_LAZY_EXPIRE_AFTER_WRITE) + .map(s -> { + try { + return Long.parseLong(s); + } catch (final NumberFormatException e) { + LOG.warn("Unable to set {} to: {}. Using default: ", PARAM_NAME_LAZY_EXPIRE_AFTER_WRITE, s, DEFAULT_LAZY_EXPIRE_AFTER_WRITE); + return DEFAULT_LAZY_EXPIRE_AFTER_ACCESS; + } + }); + - return Optional.of(new CacheConfig(permissions, maximumSize, expireAfterAccess)); + return Optional.of(new CacheConfig(permissions, maximumSize, expireAfterAccess, expireAfterWrite)); } private static Optional getFirstString(final Map> parameters, final String paramName) { diff --git a/extensions/modules/cache/src/test/xquery/modules/cache/cache.xqm b/extensions/modules/cache/src/test/xquery/modules/cache/cache.xqm index 248438f99c1..351449524c5 100644 --- a/extensions/modules/cache/src/test/xquery/modules/cache/cache.xqm +++ b/extensions/modules/cache/src/test/xquery/modules/cache/cache.xqm @@ -33,6 +33,8 @@ declare variable $c:maximumSize := 5; declare variable $c:maximumSize-options := map { "maximumSize": $c:maximumSize }; declare variable $c:expireAfterAccess := 1000; declare variable $c:expireAfterAccess-options := map { "expireAfterAccess": $c:expireAfterAccess }; +declare variable $c:expireAfterWrite := 1000; +declare variable $c:expireAfterWrite-options := map { "expireAfterWrite": $c:expireAfterWrite }; declare function c:_create-simple() { cache:create($c:cache-name, $c:simple-options) @@ -46,6 +48,10 @@ declare function c:_create-expireAfterAccess() { cache:create($c:cache-name, $c:expireAfterAccess-options) }; +declare function c:_create-expireAfterWrite() { + cache:create($c:cache-name, $c:expireAfterWrite-options) +}; + declare function c:_populate($size as xs:integer) { (1 to $size) ! cache:put($c:cache-name, "foo" || ., "bar" || .) }; @@ -171,6 +177,21 @@ function c:exercise-expireAfterAccess() { count(c:_keys()) }; +declare + %test:assertEquals(0) +function c:exercise-expireAfterWrite() { + let $setup := + ( + c:_destroy(), + c:_create-expireAfterWrite(), + c:_populate(5), + util:wait($c:expireAfterWrite * 1.1), + c:_cleanup() + ) + return + count(c:_keys()) +}; + declare %test:assertEquals("bar5") function c:get() {