mirror of https://github.com/halo-dev/halo
Refactor CacheStore
parent
8d82a03e90
commit
68b78b9267
|
@ -0,0 +1,104 @@
|
||||||
|
package cc.ryanc.halo.cache;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Abstract cache store.
|
||||||
|
*
|
||||||
|
* @author johnniang
|
||||||
|
* @date 3/28/19
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public abstract class AbstractCacheStore<K, V> implements CacheStore<K, V> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get cache wrapper by key.
|
||||||
|
*
|
||||||
|
* @param key key must not be null
|
||||||
|
* @return an optional cache wrapper
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
abstract Optional<CacheWrapper<V>> getInternal(@NonNull K key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts the cache wrapper.
|
||||||
|
*
|
||||||
|
* @param key key must not be null
|
||||||
|
* @param cacheWrapper cache wrapper must not be null
|
||||||
|
*/
|
||||||
|
abstract void putInternal(@NonNull K key, @NonNull CacheWrapper<V> cacheWrapper);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<V> get(K key) {
|
||||||
|
Assert.notNull(key, "Cache key must not be blank");
|
||||||
|
|
||||||
|
return getInternal(key).map(cacheWrapper -> {
|
||||||
|
log.debug("Cache wrapper: [{}]", cacheWrapper);
|
||||||
|
|
||||||
|
// Check expiration
|
||||||
|
if (cacheWrapper.getExpireAt() != null && cacheWrapper.getExpireAt().before(cc.ryanc.halo.utils.DateUtils.now())) {
|
||||||
|
// Expired then delete it
|
||||||
|
log.warn("Cache key: [{}] has been expired", key);
|
||||||
|
|
||||||
|
// Delete the key
|
||||||
|
delete(key);
|
||||||
|
|
||||||
|
// Return null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cacheWrapper.getData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(K key, V value, long timeout, TimeUnit timeUnit) {
|
||||||
|
Assert.notNull(key, "Cache key must not be blank");
|
||||||
|
Assert.notNull(value, "Cache value must not be null");
|
||||||
|
Assert.isTrue(timeout > 0, "Cache expiration timeout must not be less than 1");
|
||||||
|
Assert.notNull(timeUnit, "Time unit must not be null");
|
||||||
|
|
||||||
|
// Handle expiration
|
||||||
|
Date now = cc.ryanc.halo.utils.DateUtils.now();
|
||||||
|
|
||||||
|
long millis = timeUnit.toMillis(timeout);
|
||||||
|
if (millis <= 0) {
|
||||||
|
millis = 1L;
|
||||||
|
}
|
||||||
|
|
||||||
|
Date expireAt = DateUtils.addMilliseconds(now, Long.valueOf(millis).intValue());
|
||||||
|
|
||||||
|
// Build cache wrapper
|
||||||
|
CacheWrapper<V> cacheWrapper = new CacheWrapper<>();
|
||||||
|
cacheWrapper.setCreateAt(now);
|
||||||
|
cacheWrapper.setExpireAt(expireAt);
|
||||||
|
cacheWrapper.setData(value);
|
||||||
|
|
||||||
|
putInternal(key, cacheWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void put(K key, V value) {
|
||||||
|
Assert.notNull(key, "Cache key must not be blank");
|
||||||
|
Assert.notNull(value, "Cache value must not be null");
|
||||||
|
|
||||||
|
// Get current time
|
||||||
|
Date now = cc.ryanc.halo.utils.DateUtils.now();
|
||||||
|
|
||||||
|
// Build cache wrapper
|
||||||
|
CacheWrapper<V> cacheWrapper = new CacheWrapper<>();
|
||||||
|
cacheWrapper.setCreateAt(now);
|
||||||
|
cacheWrapper.setExpireAt(null);
|
||||||
|
cacheWrapper.setData(value);
|
||||||
|
|
||||||
|
putInternal(key, cacheWrapper);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,15 +25,23 @@ public interface CacheStore<K, V> {
|
||||||
Optional<V> get(@NonNull K key);
|
Optional<V> get(@NonNull K key);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Puts a cache.
|
* Puts a cache which will be expired.
|
||||||
*
|
*
|
||||||
* @param key cache key must not be null
|
* @param key cache key must not be null
|
||||||
* @param value cache value must not be null
|
* @param value cache value must not be null
|
||||||
* @param timeout the key expiration must not be less than 0
|
* @param timeout the key expiration must not be less than 1
|
||||||
* @param timeUnit timeout unit
|
* @param timeUnit timeout unit
|
||||||
*/
|
*/
|
||||||
void put(@NonNull K key, @NonNull V value, long timeout, @NonNull TimeUnit timeUnit);
|
void put(@NonNull K key, @NonNull V value, long timeout, @NonNull TimeUnit timeUnit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Puts a non-expired cache.
|
||||||
|
*
|
||||||
|
* @param key cache key must not be null
|
||||||
|
* @param value cache value must not be null
|
||||||
|
*/
|
||||||
|
void put(@NonNull K key, @NonNull V value);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a key.
|
* Delete a key.
|
||||||
*
|
*
|
||||||
|
|
|
@ -14,17 +14,12 @@ import java.util.Date;
|
||||||
@ToString
|
@ToString
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class CacheWrapper<T> {
|
public class CacheWrapper<V> {
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache key.
|
|
||||||
*/
|
|
||||||
private String key;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache data
|
* Cache data
|
||||||
*/
|
*/
|
||||||
private T data;
|
private V data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expired time.
|
* Expired time.
|
||||||
|
|
|
@ -1,31 +1,46 @@
|
||||||
package cc.ryanc.halo.cache;
|
package cc.ryanc.halo.cache;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* In-memory cache store.
|
* In-memory cache store.
|
||||||
*
|
*
|
||||||
* @author johnniang
|
* @author johnniang
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public class InMemoryCacheStore extends StringCacheStore {
|
public class InMemoryCacheStore extends StringCacheStore {
|
||||||
|
|
||||||
private final static ConcurrentHashMap<String, String> cacheContainer = new ConcurrentHashMap<>();
|
/**
|
||||||
|
* Cache container.
|
||||||
|
*/
|
||||||
|
private final static ConcurrentHashMap<String, CacheWrapper<String>> cacheContainer = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Optional<String> get(String key) {
|
Optional<CacheWrapper<String>> getInternal(String key) {
|
||||||
|
Assert.hasText(key, "Cache key must not be blank");
|
||||||
|
|
||||||
return Optional.ofNullable(cacheContainer.get(key));
|
return Optional.ofNullable(cacheContainer.get(key));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void put(String key, String value, long timeout, TimeUnit timeUnit) {
|
void putInternal(String key, CacheWrapper<String> cacheWrapper) {
|
||||||
cacheContainer.put(key, value);
|
Assert.hasText(key, "Cache key must not be blank");
|
||||||
|
Assert.notNull(cacheWrapper, "Cache wrapper must not be null");
|
||||||
|
|
||||||
|
// Put the cache wrapper
|
||||||
|
CacheWrapper<String> putCacheWrapper = cacheContainer.put(key, cacheWrapper);
|
||||||
|
|
||||||
|
log.debug("Put cache wrapper: [{}]", putCacheWrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete(String key) {
|
public void delete(String key) {
|
||||||
// TODO Consider to delete the cache periodic
|
Assert.hasText(key, "Cache key must not be blank");
|
||||||
|
|
||||||
cacheContainer.remove(key);
|
cacheContainer.remove(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,6 @@
|
||||||
package cc.ryanc.halo.cache;
|
package cc.ryanc.halo.cache;
|
||||||
|
|
||||||
import cc.ryanc.halo.exception.ServiceException;
|
|
||||||
import cc.ryanc.halo.utils.JsonUtils;
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.time.DateUtils;
|
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* String cache store.
|
* String cache store.
|
||||||
|
@ -19,83 +8,6 @@ import java.util.concurrent.TimeUnit;
|
||||||
* @author johnniang
|
* @author johnniang
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class StringCacheStore implements CacheStore<String, String> {
|
public abstract class StringCacheStore extends AbstractCacheStore<String, String> {
|
||||||
|
|
||||||
public <T> void putForString(String key, T value, long timeout, TimeUnit timeUnit) {
|
|
||||||
Assert.hasText(key, "Cache key must not be blank");
|
|
||||||
Assert.notNull(value, "Cache value must not be null");
|
|
||||||
Assert.isTrue(timeout > 0, "Timeout must not be less than 0");
|
|
||||||
Assert.notNull(timeUnit, "Time unit must not be null");
|
|
||||||
|
|
||||||
// Convert to second
|
|
||||||
Long seconds = timeUnit.toSeconds(timeout);
|
|
||||||
|
|
||||||
// Round the seconds
|
|
||||||
if (seconds == 0) {
|
|
||||||
seconds = 1L;
|
|
||||||
}
|
|
||||||
|
|
||||||
Date now = new Date();
|
|
||||||
|
|
||||||
// Calculate expire at
|
|
||||||
Date expireAt = DateUtils.addSeconds(now, seconds.intValue());
|
|
||||||
|
|
||||||
// Build cache wrapper
|
|
||||||
CacheWrapper<T> wrapper = new CacheWrapper<>();
|
|
||||||
wrapper.setCreateAt(now);
|
|
||||||
wrapper.setExpireAt(expireAt);
|
|
||||||
wrapper.setKey(key);
|
|
||||||
wrapper.setData(value);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Convert wrapper to json
|
|
||||||
String valueJson = JsonUtils.objectToJson(wrapper);
|
|
||||||
// Put the the value json to cache store
|
|
||||||
put(key, valueJson, timeout, timeUnit);
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
throw new ServiceException("Failed to convert object to json", e).setErrorData(wrapper);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@NonNull
|
|
||||||
public <T> Optional<T> getForString(@NonNull String key, @NonNull Class<T> type) {
|
|
||||||
Assert.hasText(key, "Cache key must not be blank");
|
|
||||||
Assert.notNull(type, "Cache type must not be null");
|
|
||||||
|
|
||||||
return get(key).map(value -> {
|
|
||||||
try {
|
|
||||||
CacheWrapper<?> cacheWrapper = JsonUtils.jsonToObject(value, CacheWrapper.class);
|
|
||||||
|
|
||||||
if (cacheWrapper == null) {
|
|
||||||
log.error("Cache wrapper is null, key: [{}]", key);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.debug("Cache wrapper: [{}]", cacheWrapper);
|
|
||||||
|
|
||||||
Date now = new Date();
|
|
||||||
|
|
||||||
if (cacheWrapper.getExpireAt().before(now)) {
|
|
||||||
// Expired then delete it
|
|
||||||
log.debug("Cache key: [{}] has been expired", key);
|
|
||||||
|
|
||||||
delete(key);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object data = cacheWrapper.getData();
|
|
||||||
|
|
||||||
if (data != null && data.getClass().isAssignableFrom(type)) {
|
|
||||||
return (T) data;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.error("Data type: [{}], but specified type: [{}]", data == null ? null : data.getClass(), type);
|
|
||||||
throw new ServiceException("Cache value type is mismatched with the specified type");
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new ServiceException("Failed to convert from json to object", e).setErrorData(value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,98 @@
|
||||||
|
package cc.ryanc.halo.cache;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InMemoryCacheStoreTest.
|
||||||
|
*
|
||||||
|
* @author johnniang
|
||||||
|
* @date 3/28/19
|
||||||
|
*/
|
||||||
|
public class InMemoryCacheStoreTest {
|
||||||
|
|
||||||
|
private InMemoryCacheStore cacheStore;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
cacheStore = new InMemoryCacheStore();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void putNullValueTest() {
|
||||||
|
String key = "test_key";
|
||||||
|
|
||||||
|
cacheStore.put(key, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void putNullKeyTest() {
|
||||||
|
String value = "test_value";
|
||||||
|
|
||||||
|
cacheStore.put(null, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void getByNullKeyTest() {
|
||||||
|
cacheStore.get(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getNullTest() {
|
||||||
|
String key = "test_key";
|
||||||
|
|
||||||
|
Optional<String> valueOptional = cacheStore.get(key);
|
||||||
|
|
||||||
|
assertFalse(valueOptional.isPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void expirationTest() throws InterruptedException {
|
||||||
|
String key = "test_key";
|
||||||
|
String value = "test_value";
|
||||||
|
cacheStore.put(key, value, 500, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
Optional<String> valueOptional = cacheStore.get(key);
|
||||||
|
|
||||||
|
assertTrue(valueOptional.isPresent());
|
||||||
|
assertThat(valueOptional.get(), equalTo(value));
|
||||||
|
|
||||||
|
TimeUnit.SECONDS.sleep(1L);
|
||||||
|
|
||||||
|
valueOptional = cacheStore.get(key);
|
||||||
|
|
||||||
|
assertFalse(valueOptional.isPresent());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteTest() {
|
||||||
|
String key = "test_key";
|
||||||
|
String value = "test_value";
|
||||||
|
|
||||||
|
// Put the cache
|
||||||
|
cacheStore.put(key, value);
|
||||||
|
|
||||||
|
// Get the caceh
|
||||||
|
Optional<String> valueOptional = cacheStore.get(key);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
assertTrue(valueOptional.isPresent());
|
||||||
|
assertThat(valueOptional.get(), equalTo(value));
|
||||||
|
|
||||||
|
// Delete the cache
|
||||||
|
cacheStore.delete(key);
|
||||||
|
|
||||||
|
// Get the cache again
|
||||||
|
valueOptional = cacheStore.get(key);
|
||||||
|
|
||||||
|
// Assertion
|
||||||
|
assertFalse(valueOptional.isPresent());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue