diff --git a/src/main/java/com/navercorp/arcus/spring/cache/ArcusCache.java b/src/main/java/com/navercorp/arcus/spring/cache/ArcusCache.java index e7621b8..fa1336f 100644 --- a/src/main/java/com/navercorp/arcus/spring/cache/ArcusCache.java +++ b/src/main/java/com/navercorp/arcus/spring/cache/ArcusCache.java @@ -35,7 +35,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; -import org.springframework.cache.Cache; +import org.springframework.cache.support.AbstractValueAdaptingCache; import org.springframework.cache.support.SimpleValueWrapper; import org.springframework.util.Assert; import org.springframework.util.DigestUtils; @@ -76,10 +76,12 @@ *

*/ @SuppressWarnings("DeprecatedIsStillUsed") -public class ArcusCache implements Cache, InitializingBean { +public class ArcusCache extends AbstractValueAdaptingCache implements InitializingBean { public static final long DEFAULT_TIMEOUT_MILLISECONDS = 700L; + public static final boolean DEFAULT_ALLOW_NULL_VALUES = true; + private final Logger logger = LoggerFactory.getLogger(this.getClass()); private String name; @@ -97,10 +99,11 @@ public class ArcusCache implements Cache, InitializingBean { private ArcusFrontCache arcusFrontCache; public ArcusCache() { - + super(DEFAULT_ALLOW_NULL_VALUES); } ArcusCache(String name, ArcusClientPool clientPool, ArcusCacheConfiguration configuration) { + super(configuration.isAllowNullValues()); this.name = name; this.arcusClient = clientPool; this.serviceId = configuration.getServiceId(); @@ -128,33 +131,25 @@ public Object getNativeCache() { @Nullable @Override - public ValueWrapper get(Object key) { - Object value = null; + protected Object lookup(Object key) { try { - value = getValue(createArcusKey(key)); + return getValue(createArcusKey(key)); } catch (Exception e) { - logger.info(e.getMessage()); - if (wantToGetException) { - throw toRuntimeException(e); - } + throw toRuntimeException(e); } - return (value != null ? new SimpleValueWrapper(value) : null); } - @SuppressWarnings("unchecked") @Nullable @Override - public T get(Object key, Class type) { + public ValueWrapper get(Object key) { try { - Object value = getValue(createArcusKey(key)); - if (value != null && type != null && !type.isInstance(value)) { - throw new IllegalStateException( - "Cached value is not of required type [" + type.getName() + "]: " + value); - } - return (T) value; - } catch (Exception e) { + return super.get(key); + } catch (RuntimeException e) { logger.info(e.getMessage()); - throw toRuntimeException(e); + if (wantToGetException) { + throw e; + } + return null; } } @@ -162,36 +157,40 @@ public T get(Object key, Class type) { @Nullable @Override public T get(Object key, Callable valueLoader) { + ValueWrapper result = super.get(key); + + return result != null ? (T) result.get() : getSynchronized(key, valueLoader); + } + + @Nullable + @SuppressWarnings("unchecked") + private T getSynchronized(Object key, Callable valueLoader) { String arcusKey = createArcusKey(key); - Object value; try { - value = getValue(arcusKey); - if (value != null) { - return (T) value; - } + acquireWriteLockOnKey(arcusKey); + ValueWrapper result = super.get(key); + return result != null ? (T) result.get() : loadValue(arcusKey, valueLoader); + } finally { + releaseWriteLockOnKey(arcusKey); + } + } + + private T loadValue(String key, Callable valueLoader) { + T value; + try { + value = valueLoader.call(); } catch (Exception e) { - logger.info(e.getMessage()); - throw toRuntimeException(e); + throw new ValueRetrievalException(key, valueLoader, e); } try { - acquireWriteLockOnKey(arcusKey); - value = getValue(arcusKey); - if (value == null) { - try { - value = valueLoader.call(); - } catch (Exception e) { - throw new ValueRetrievalException(key, valueLoader, e); - } - putValue(arcusKey, value); - } - return (T) value; + putValue(key, value); } catch (Exception e) { logger.info(e.getMessage()); throw toRuntimeException(e); - } finally { - releaseWriteLockOnKey(arcusKey); } + + return value; } @Override diff --git a/src/main/java/com/navercorp/arcus/spring/cache/ArcusCacheConfiguration.java b/src/main/java/com/navercorp/arcus/spring/cache/ArcusCacheConfiguration.java index b40ca6b..62e6649 100644 --- a/src/main/java/com/navercorp/arcus/spring/cache/ArcusCacheConfiguration.java +++ b/src/main/java/com/navercorp/arcus/spring/cache/ArcusCacheConfiguration.java @@ -36,6 +36,7 @@ public class ArcusCacheConfiguration implements InitializingBean { private Transcoder operationTranscoder; private ArcusFrontCache arcusFrontCache; private boolean forceFrontCaching; + private boolean allowNullValues = ArcusCache.DEFAULT_ALLOW_NULL_VALUES; public String getServiceId() { return serviceId; @@ -104,6 +105,14 @@ public void setForceFrontCaching(boolean forceFrontCaching) { this.forceFrontCaching = forceFrontCaching; } + public boolean isAllowNullValues() { + return this.allowNullValues; + } + + public void setAllowNullValues(boolean allowNullValues) { + this.allowNullValues = allowNullValues; + } + @Override public void afterPropertiesSet() { Assert.isTrue(timeoutMilliSeconds > 0, "TimeoutMilliSeconds must be larger than 0."); diff --git a/src/test/java/com/navercorp/arcus/spring/cache/ArcusCacheTest.java b/src/test/java/com/navercorp/arcus/spring/cache/ArcusCacheTest.java index 3cc9f79..15f445f 100644 --- a/src/test/java/com/navercorp/arcus/spring/cache/ArcusCacheTest.java +++ b/src/test/java/com/navercorp/arcus/spring/cache/ArcusCacheTest.java @@ -20,6 +20,8 @@ import com.navercorp.arcus.spring.cache.front.ArcusFrontCache; import com.navercorp.arcus.spring.concurrent.KeyLockProvider; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; @@ -225,6 +227,25 @@ public void testGet_FrontCache_Null() { assertNull(value); } + @Test + public void testGet_NullValue() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + // given + Method toStoreValue = arcusCache.getClass().getSuperclass().getDeclaredMethod("toStoreValue", Object.class); + toStoreValue.setAccessible(true); + Object nullValue = toStoreValue.invoke(arcusCache, (Object) null); + when(arcusClientPool.asyncGet(arcusKey)) + .thenReturn(createGetFuture(nullValue)); + + // when + Cache.ValueWrapper value = arcusCache.get(ARCUS_STRING_KEY); + + // then + verify(arcusClientPool, times(1)) + .asyncGet(arcusKey); + assertNotNull(value); + assertNull(value.get()); + } + @Test public void testPut() { // given