Add an access-tracking cache to be used in rules (#2887)
Signed-off-by: Jan N. Klug <github@klug.nrw>pull/3147/head
parent
dc2f5d54f4
commit
028724a73f
|
@ -0,0 +1,246 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.automation.module.script.rulesupport.internal;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.core.automation.module.script.ScriptExtensionProvider;
|
||||||
|
import org.openhab.core.automation.module.script.rulesupport.shared.ValueCache;
|
||||||
|
import org.openhab.core.common.ThreadPoolManager;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link CacheScriptExtension} extends scripts to use a cache shared between rules or subsequent runs of the same
|
||||||
|
* rule
|
||||||
|
*
|
||||||
|
* @author Jan N. Klug - Initial contribution
|
||||||
|
*/
|
||||||
|
@Component(immediate = true)
|
||||||
|
@NonNullByDefault
|
||||||
|
public class CacheScriptExtension implements ScriptExtensionProvider {
|
||||||
|
static final String PRESET_NAME = "cache";
|
||||||
|
static final String SHARED_CACHE_NAME = "sharedCache";
|
||||||
|
static final String PRIVATE_CACHE_NAME = "privateCache";
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(CacheScriptExtension.class);
|
||||||
|
private final ScheduledExecutorService scheduler = ThreadPoolManager
|
||||||
|
.getScheduledPool(ThreadPoolManager.THREAD_POOL_NAME_COMMON);
|
||||||
|
|
||||||
|
private final Lock cacheLock = new ReentrantLock();
|
||||||
|
private final Map<String, Object> sharedCache = new HashMap<>();
|
||||||
|
private final Map<String, Set<String>> sharedCacheKeyAccessors = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private final Map<String, ValueCacheImpl> privateCaches = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public CacheScriptExtension() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getDefaultPresets() {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getPresets() {
|
||||||
|
return Set.of(PRESET_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> getTypes() {
|
||||||
|
return Set.of(PRIVATE_CACHE_NAME, SHARED_CACHE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Object get(String scriptIdentifier, String type) throws IllegalArgumentException {
|
||||||
|
if (SHARED_CACHE_NAME.equals(type)) {
|
||||||
|
return new TrackingValueCacheImpl(scriptIdentifier);
|
||||||
|
} else if (PRIVATE_CACHE_NAME.equals(type)) {
|
||||||
|
return privateCaches.computeIfAbsent(scriptIdentifier, ValueCacheImpl::new);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> importPreset(String scriptIdentifier, String preset) {
|
||||||
|
if (PRESET_NAME.equals(preset)) {
|
||||||
|
Object privateCache = Objects
|
||||||
|
.requireNonNull(privateCaches.computeIfAbsent(scriptIdentifier, ValueCacheImpl::new));
|
||||||
|
return Map.of(SHARED_CACHE_NAME, new TrackingValueCacheImpl(scriptIdentifier), PRIVATE_CACHE_NAME,
|
||||||
|
privateCache);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void unload(String scriptIdentifier) {
|
||||||
|
cacheLock.lock();
|
||||||
|
try {
|
||||||
|
// remove the scriptIdentifier from cache-key access list
|
||||||
|
sharedCacheKeyAccessors.values().forEach(cacheKey -> cacheKey.remove(scriptIdentifier));
|
||||||
|
// remove the key from access list and cache if no accessor left
|
||||||
|
Iterator<Map.Entry<String, Set<String>>> it = sharedCacheKeyAccessors.entrySet().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
Map.Entry<String, Set<String>> element = it.next();
|
||||||
|
if (element.getValue().isEmpty()) {
|
||||||
|
// accessor list is empty
|
||||||
|
it.remove();
|
||||||
|
// remove from cache and cancel ScheduledFutures or Timer tasks
|
||||||
|
asyncCancelJob(sharedCache.remove(element.getKey()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
cacheLock.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove private cache
|
||||||
|
ValueCacheImpl privateCache = privateCaches.remove(scriptIdentifier);
|
||||||
|
if (privateCache != null) {
|
||||||
|
// cancel ScheduledFutures or Timer tasks
|
||||||
|
privateCache.values().forEach(this::asyncCancelJob);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if object is {@link ScheduledFuture} or {@link Timer} and schedule cancellation of those jobs
|
||||||
|
*
|
||||||
|
* @param o the {@link Object} to check
|
||||||
|
*/
|
||||||
|
private void asyncCancelJob(@Nullable Object o) {
|
||||||
|
if (o instanceof ScheduledFuture) {
|
||||||
|
scheduler.execute(() -> ((ScheduledFuture<?>) o).cancel(true));
|
||||||
|
} else if (o instanceof Timer) {
|
||||||
|
// not using execute so ensure this operates in another thread and we don't block here
|
||||||
|
scheduler.schedule(() -> ((Timer) o).cancel(), 0, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ValueCacheImpl implements ValueCache {
|
||||||
|
private final Map<String, Object> cache = new HashMap<>();
|
||||||
|
|
||||||
|
public ValueCacheImpl(String scriptIdentifier) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Object put(String key, Object value) {
|
||||||
|
return cache.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Object remove(String key) {
|
||||||
|
return cache.remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Object get(String key) {
|
||||||
|
return cache.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object get(String key, Supplier<Object> supplier) {
|
||||||
|
return Objects.requireNonNull(cache.computeIfAbsent(key, k -> supplier.get()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<Object> values() {
|
||||||
|
return cache.values();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TrackingValueCacheImpl implements ValueCache {
|
||||||
|
private final String scriptIdentifier;
|
||||||
|
|
||||||
|
public TrackingValueCacheImpl(String scriptIdentifier) {
|
||||||
|
this.scriptIdentifier = scriptIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Object put(String key, Object value) {
|
||||||
|
cacheLock.lock();
|
||||||
|
try {
|
||||||
|
rememberAccessToKey(key);
|
||||||
|
Object oldValue = sharedCache.put(key, value);
|
||||||
|
logger.trace("PUT to cache from '{}': '{}' -> '{}' (was: '{}')", scriptIdentifier, key, value,
|
||||||
|
oldValue);
|
||||||
|
return oldValue;
|
||||||
|
} finally {
|
||||||
|
cacheLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Object remove(String key) {
|
||||||
|
cacheLock.lock();
|
||||||
|
try {
|
||||||
|
sharedCacheKeyAccessors.remove(key);
|
||||||
|
Object oldValue = sharedCache.remove(key);
|
||||||
|
|
||||||
|
logger.trace("REMOVE from cache from '{}': '{}' -> '{}'", scriptIdentifier, key, oldValue);
|
||||||
|
return oldValue;
|
||||||
|
} finally {
|
||||||
|
cacheLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable Object get(String key) {
|
||||||
|
cacheLock.lock();
|
||||||
|
try {
|
||||||
|
rememberAccessToKey(key);
|
||||||
|
Object value = sharedCache.get(key);
|
||||||
|
|
||||||
|
logger.trace("GET to cache from '{}': '{}' -> '{}'", scriptIdentifier, key, value);
|
||||||
|
return value;
|
||||||
|
} finally {
|
||||||
|
cacheLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object get(String key, Supplier<Object> supplier) {
|
||||||
|
cacheLock.lock();
|
||||||
|
try {
|
||||||
|
rememberAccessToKey(key);
|
||||||
|
Object value = Objects.requireNonNull(sharedCache.computeIfAbsent(key, k -> supplier.get()));
|
||||||
|
|
||||||
|
logger.trace("GET with supplier to cache from '{}': '{}' -> '{}'", scriptIdentifier, key, value);
|
||||||
|
return value;
|
||||||
|
} finally {
|
||||||
|
cacheLock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void rememberAccessToKey(String key) {
|
||||||
|
Objects.requireNonNull(sharedCacheKeyAccessors.computeIfAbsent(key, k -> new HashSet<>()))
|
||||||
|
.add(scriptIdentifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,65 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.automation.module.script.rulesupport.shared;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ValueCache} can be used by scripts to share information between subsequent runs of the same script or
|
||||||
|
* between scripts (depending on implementation).
|
||||||
|
*
|
||||||
|
* @author Jan N. Klug - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public interface ValueCache {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new key-value-pair to the cache. If the key is already present, the old value is replaces by the new value.
|
||||||
|
*
|
||||||
|
* @param key a string used as key
|
||||||
|
* @param value an {@code Object} to store with the key
|
||||||
|
* @return the old value associated with this key or {@code null} if key didn't exist
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
Object put(String key, Object value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a key (and its associated value) from the cache
|
||||||
|
*
|
||||||
|
* @param key the key to remove
|
||||||
|
* @return the previously associated value to this key or {@code null} if key not present
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
Object remove(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a value from the cache
|
||||||
|
*
|
||||||
|
* @param key the key of the requested value
|
||||||
|
* @return the value associated with the key or {@code null} if key not present
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
Object get(String key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a value from the cache or create a new key-value-pair from the given supplier
|
||||||
|
*
|
||||||
|
* @param key the key of the requested value
|
||||||
|
* @param supplier a supplier that returns a non-null value to be used if the key was not present
|
||||||
|
* @return the value associated with the key
|
||||||
|
*/
|
||||||
|
Object get(String key, Supplier<Object> supplier);
|
||||||
|
}
|
|
@ -0,0 +1,217 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.core.automation.module.script.rulesupport.internal;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
import static org.hamcrest.Matchers.sameInstance;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.timeout;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Timer;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.openhab.core.automation.module.script.rulesupport.shared.ValueCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link CacheScriptExtensionTest} contains tests for {@link CacheScriptExtension}
|
||||||
|
*
|
||||||
|
* @author Jan N. Klug - Initial contribution
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
public class CacheScriptExtensionTest {
|
||||||
|
private static final String SCRIPT1 = "script1";
|
||||||
|
private static final String SCRIPT2 = "script2";
|
||||||
|
|
||||||
|
private static final String KEY1 = "key1";
|
||||||
|
private static final String KEY2 = "key2";
|
||||||
|
|
||||||
|
private static final String VALUE1 = "value1";
|
||||||
|
private static final String VALUE2 = "value2";
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sharedCacheBasicFunction() {
|
||||||
|
CacheScriptExtension se = new CacheScriptExtension();
|
||||||
|
ValueCache cache = getCache(se, SCRIPT1, CacheScriptExtension.SHARED_CACHE_NAME);
|
||||||
|
|
||||||
|
testCacheBasicFunctions(cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void privateCacheBasicFunction() {
|
||||||
|
CacheScriptExtension se = new CacheScriptExtension();
|
||||||
|
ValueCache cache = getCache(se, SCRIPT1, CacheScriptExtension.PRIVATE_CACHE_NAME);
|
||||||
|
|
||||||
|
testCacheBasicFunctions(cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sharedCacheIsSharedBetweenTwoRuns() {
|
||||||
|
CacheScriptExtension se = new CacheScriptExtension();
|
||||||
|
ValueCache cache1 = getCache(se, SCRIPT1, CacheScriptExtension.SHARED_CACHE_NAME);
|
||||||
|
Objects.requireNonNull(cache1);
|
||||||
|
|
||||||
|
cache1.put(KEY1, VALUE1);
|
||||||
|
assertThat(cache1.get(KEY1), is(VALUE1));
|
||||||
|
|
||||||
|
ValueCache cache2 = getCache(se, SCRIPT1, CacheScriptExtension.SHARED_CACHE_NAME);
|
||||||
|
assertThat(cache2, not(sameInstance(cache1)));
|
||||||
|
|
||||||
|
assertThat(cache2.get(KEY1), is(VALUE1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sharedCacheIsClearedIfScriptUnloaded() {
|
||||||
|
CacheScriptExtension se = new CacheScriptExtension();
|
||||||
|
ValueCache cache1 = getCache(se, SCRIPT1, CacheScriptExtension.SHARED_CACHE_NAME);
|
||||||
|
|
||||||
|
cache1.put(KEY1, VALUE1);
|
||||||
|
assertThat(cache1.get(KEY1), is(VALUE1));
|
||||||
|
|
||||||
|
se.unload(SCRIPT1);
|
||||||
|
|
||||||
|
ValueCache cache1new = getCache(se, SCRIPT2, CacheScriptExtension.SHARED_CACHE_NAME);
|
||||||
|
assertThat(cache1new.get(KEY1), nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sharedCachesIsSharedBetweenTwoScripts() {
|
||||||
|
CacheScriptExtension se = new CacheScriptExtension();
|
||||||
|
ValueCache cache1 = getCache(se, SCRIPT1, CacheScriptExtension.SHARED_CACHE_NAME);
|
||||||
|
ValueCache cache2 = getCache(se, SCRIPT2, CacheScriptExtension.SHARED_CACHE_NAME);
|
||||||
|
|
||||||
|
assertThat(cache1, not(is(cache2)));
|
||||||
|
|
||||||
|
cache1.put(KEY1, VALUE1);
|
||||||
|
assertThat(cache1.get(KEY1), is(VALUE1));
|
||||||
|
assertThat(cache2.get(KEY1), is(VALUE1));
|
||||||
|
|
||||||
|
cache2.remove(KEY1);
|
||||||
|
assertThat(cache2.get(KEY1), nullValue());
|
||||||
|
assertThat(cache1.get(KEY1), nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void privateCacheIsSharedBetweenTwoRuns() {
|
||||||
|
CacheScriptExtension se = new CacheScriptExtension();
|
||||||
|
ValueCache cache1 = getCache(se, SCRIPT1, CacheScriptExtension.PRIVATE_CACHE_NAME);
|
||||||
|
|
||||||
|
cache1.put(KEY1, VALUE1);
|
||||||
|
assertThat(cache1.get(KEY1), is(VALUE1));
|
||||||
|
|
||||||
|
ValueCache cache2 = getCache(se, SCRIPT1, CacheScriptExtension.PRIVATE_CACHE_NAME);
|
||||||
|
assertThat(cache2, sameInstance(cache1));
|
||||||
|
|
||||||
|
assertThat(cache2.get(KEY1), is(VALUE1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void privateCacheIsClearedIfScriptUnloaded() {
|
||||||
|
CacheScriptExtension se = new CacheScriptExtension();
|
||||||
|
ValueCache cache1 = getCache(se, SCRIPT1, CacheScriptExtension.PRIVATE_CACHE_NAME);
|
||||||
|
|
||||||
|
cache1.put(KEY1, VALUE1);
|
||||||
|
assertThat(cache1.get(KEY1), is(VALUE1));
|
||||||
|
|
||||||
|
se.unload(SCRIPT1);
|
||||||
|
|
||||||
|
ValueCache cache1new = getCache(se, SCRIPT2, CacheScriptExtension.PRIVATE_CACHE_NAME);
|
||||||
|
assertThat(cache1new, not(sameInstance(cache1)));
|
||||||
|
assertThat(cache1new.get(KEY1), nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void privateCachesIsNotSharedBetweenTwoScripts() {
|
||||||
|
CacheScriptExtension se = new CacheScriptExtension();
|
||||||
|
ValueCache cache1 = getCache(se, SCRIPT1, CacheScriptExtension.PRIVATE_CACHE_NAME);
|
||||||
|
ValueCache cache2 = getCache(se, SCRIPT2, CacheScriptExtension.PRIVATE_CACHE_NAME);
|
||||||
|
|
||||||
|
assertThat(cache1, not(is(cache2)));
|
||||||
|
|
||||||
|
cache1.put(KEY1, VALUE1);
|
||||||
|
assertThat(cache1.get(KEY1), is(VALUE1));
|
||||||
|
assertThat(cache2.get(KEY1), nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void jobsInSharedCacheAreCancelledOnUnload() {
|
||||||
|
testJobCancellation(CacheScriptExtension.SHARED_CACHE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void jobsInPrivateCacheAreCancelledOnUnload() {
|
||||||
|
testJobCancellation(CacheScriptExtension.PRIVATE_CACHE_NAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testJobCancellation(String cacheType) {
|
||||||
|
CacheScriptExtension se = new CacheScriptExtension();
|
||||||
|
ValueCache cache = getCache(se, SCRIPT1, cacheType);
|
||||||
|
|
||||||
|
Timer timerMock = mock(Timer.class);
|
||||||
|
ScheduledFuture<?> futureMock = mock(ScheduledFuture.class);
|
||||||
|
|
||||||
|
cache.put(KEY1, timerMock);
|
||||||
|
cache.put(KEY2, futureMock);
|
||||||
|
|
||||||
|
// ensure jobs are not cancelled on removal
|
||||||
|
cache.remove(KEY1);
|
||||||
|
cache.remove(KEY2);
|
||||||
|
verifyNoMoreInteractions(timerMock, futureMock);
|
||||||
|
|
||||||
|
cache.put(KEY1, timerMock);
|
||||||
|
cache.put(KEY2, futureMock);
|
||||||
|
se.unload(SCRIPT1);
|
||||||
|
verify(timerMock, timeout(1000)).cancel();
|
||||||
|
verify(futureMock, timeout(1000)).cancel(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCacheBasicFunctions(ValueCache cache) {
|
||||||
|
// cache is initially empty
|
||||||
|
assertThat(cache.get(KEY1), nullValue());
|
||||||
|
|
||||||
|
// return value is null if no value before and new value can be retrieved
|
||||||
|
assertThat(cache.put(KEY1, VALUE1), nullValue());
|
||||||
|
assertThat(cache.get(KEY1), is(VALUE1));
|
||||||
|
|
||||||
|
// value returns old value on update and updated value can be retrieved
|
||||||
|
assertThat(cache.put(KEY1, VALUE2), is(VALUE1));
|
||||||
|
assertThat(cache.get(KEY1), is(VALUE2));
|
||||||
|
|
||||||
|
// old value is returned on removal and cache empty afterwards
|
||||||
|
assertThat(cache.remove(KEY1), is(VALUE2));
|
||||||
|
assertThat(cache.get(KEY1), nullValue());
|
||||||
|
|
||||||
|
// new value is inserted from supplier
|
||||||
|
assertThat(cache.get(KEY1, () -> VALUE1), is(VALUE1));
|
||||||
|
assertThat(cache.get(KEY1), is(VALUE1));
|
||||||
|
|
||||||
|
// different keys return different values
|
||||||
|
cache.put(KEY2, VALUE2);
|
||||||
|
assertThat(cache.get(KEY1), is(VALUE1));
|
||||||
|
assertThat(cache.get(KEY2), is(VALUE2));
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueCache getCache(CacheScriptExtension se, String scriptIdentifier, String type) {
|
||||||
|
ValueCache cache = (ValueCache) se.get(scriptIdentifier, type);
|
||||||
|
Objects.requireNonNull(cache);
|
||||||
|
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue