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);
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Puts a cache.
 | 
			
		||||
     * Puts a cache which will be expired.
 | 
			
		||||
     *
 | 
			
		||||
     * @param key      cache key 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
 | 
			
		||||
     */
 | 
			
		||||
    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.
 | 
			
		||||
     *
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,17 +14,12 @@ import java.util.Date;
 | 
			
		|||
@ToString
 | 
			
		||||
@NoArgsConstructor
 | 
			
		||||
@AllArgsConstructor
 | 
			
		||||
public class CacheWrapper<T> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Cache key.
 | 
			
		||||
     */
 | 
			
		||||
    private String key;
 | 
			
		||||
public class CacheWrapper<V> {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Cache data
 | 
			
		||||
     */
 | 
			
		||||
    private T data;
 | 
			
		||||
    private V data;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Expired time.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,31 +1,46 @@
 | 
			
		|||
package cc.ryanc.halo.cache;
 | 
			
		||||
 | 
			
		||||
import lombok.extern.slf4j.Slf4j;
 | 
			
		||||
import org.springframework.util.Assert;
 | 
			
		||||
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
import java.util.concurrent.ConcurrentHashMap;
 | 
			
		||||
import java.util.concurrent.TimeUnit;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * In-memory cache store.
 | 
			
		||||
 *
 | 
			
		||||
 * @author johnniang
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
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
 | 
			
		||||
    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));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void put(String key, String value, long timeout, TimeUnit timeUnit) {
 | 
			
		||||
        cacheContainer.put(key, value);
 | 
			
		||||
    void putInternal(String key, CacheWrapper<String> cacheWrapper) {
 | 
			
		||||
        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
 | 
			
		||||
    public void delete(String key) {
 | 
			
		||||
        // TODO Consider to delete the cache periodic
 | 
			
		||||
        Assert.hasText(key, "Cache key must not be blank");
 | 
			
		||||
 | 
			
		||||
        cacheContainer.remove(key);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,17 +1,6 @@
 | 
			
		|||
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 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.
 | 
			
		||||
| 
						 | 
				
			
			@ -19,83 +8,6 @@ import java.util.concurrent.TimeUnit;
 | 
			
		|||
 * @author johnniang
 | 
			
		||||
 */
 | 
			
		||||
@Slf4j
 | 
			
		||||
public abstract class StringCacheStore implements CacheStore<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);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
public abstract class StringCacheStore extends AbstractCacheStore<String, String> {
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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