/*
* Copyright 2002-2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cache.jcache.config;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.SimpleKeyGenerator;
import org.springframework.context.ApplicationContext;
import static org.junit.Assert.*;
/**
* @author Stephane Nicoll
*/
public abstract class AbstractJCacheAnnotationTests {
public static final String DEFAULT_CACHE = "default";
public static final String EXCEPTION_CACHE = "exception";
@Rule
public final TestName name = new TestName();
protected ApplicationContext ctx;
private JCacheableService<?> service;
private CacheManager cacheManager;
protected abstract ApplicationContext getApplicationContext();
@Before
public void setUp() {
ctx = getApplicationContext();
service = ctx.getBean(JCacheableService.class);
cacheManager = ctx.getBean("cacheManager", CacheManager.class);
}
@Test
public void cache() {
String keyItem = name.getMethodName();
Object first = service.cache(keyItem);
Object second = service.cache(keyItem);
assertSame(first, second);
}
@Test
public void cacheNull() {
Cache cache = getCache(DEFAULT_CACHE);
String keyItem = name.getMethodName();
assertNull(cache.get(keyItem));
Object first = service.cacheNull(keyItem);
Object second = service.cacheNull(keyItem);
assertSame(first, second);
Cache.ValueWrapper wrapper = cache.get(keyItem);
assertNotNull(wrapper);
assertSame(first, wrapper.get());
assertNull("Cached value should be null", wrapper.get());
}
@Test
public void cacheException() {
String keyItem = name.getMethodName();
Cache cache = getCache(EXCEPTION_CACHE);
Object key = createKey(keyItem);
assertNull(cache.get(key));
try {
service.cacheWithException(keyItem, true);
fail("Should have thrown an exception");
}
catch (UnsupportedOperationException e) {
// This is what we expect
}
Cache.ValueWrapper result = cache.get(key);
assertNotNull(result);
assertEquals(UnsupportedOperationException.class, result.get().getClass());
}
@Test
public void cacheExceptionVetoed() {
String keyItem = name.getMethodName();
Cache cache = getCache(EXCEPTION_CACHE);
Object key = createKey(keyItem);
assertNull(cache.get(key));
try {
service.cacheWithException(keyItem, false);
fail("Should have thrown an exception");
}
catch (NullPointerException e) {
// This is what we expect
}
assertNull(cache.get(key));
}
@Test
public void cacheCheckedException() {
String keyItem = name.getMethodName();
Cache cache = getCache(EXCEPTION_CACHE);
Object key = createKey(keyItem);
assertNull(cache.get(key));
try {
service.cacheWithCheckedException(keyItem, true);
fail("Should have thrown an exception");
}
catch (IOException e) {
// This is what we expect
}
Cache.ValueWrapper result = cache.get(key);
assertNotNull(result);
assertEquals(IOException.class, result.get().getClass());
}
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
@Test
public void cacheExceptionRewriteCallStack() {
final String keyItem = name.getMethodName();
UnsupportedOperationException first = null;
long ref = service.exceptionInvocations();
try {
service.cacheWithException(keyItem, true);
fail("Should have thrown an exception");
}
catch (UnsupportedOperationException e) {
first = e;
}
// Sanity check, this particular call has called the service
assertEquals("First call should not have been cached", ref + 1, service.exceptionInvocations());
UnsupportedOperationException second = methodInCallStack(keyItem);
// Sanity check, this particular call has *not* called the service
assertEquals("Second call should have been cached", ref + 1, service.exceptionInvocations());
assertEquals(first.getCause(), second.getCause());
assertEquals(first.getMessage(), second.getMessage());
assertFalse("Original stack must not contain any reference to methodInCallStack",
contain(first, AbstractJCacheAnnotationTests.class.getName(), "methodInCallStack"));
assertTrue("Cached stack should have been rewritten with a reference to methodInCallStack",
contain(second, AbstractJCacheAnnotationTests.class.getName(), "methodInCallStack"));
}
@Test
public void cacheAlwaysInvoke() {
String keyItem = name.getMethodName();
Object first = service.cacheAlwaysInvoke(keyItem);
Object second = service.cacheAlwaysInvoke(keyItem);
assertNotSame(first, second);
}
@Test
public void cacheWithPartialKey() {
String keyItem = name.getMethodName();
Object first = service.cacheWithPartialKey(keyItem, true);
Object second = service.cacheWithPartialKey(keyItem, false);
assertSame(first, second); // second argument not used, see config
}
@Test
public void cacheWithCustomCacheResolver() {
String keyItem = name.getMethodName();
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(keyItem);
service.cacheWithCustomCacheResolver(keyItem);
assertNull(cache.get(key)); // Cache in mock cache
}
@Test
public void cacheWithCustomKeyGenerator() {
String keyItem = name.getMethodName();
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(keyItem);
service.cacheWithCustomKeyGenerator(keyItem, "ignored");
assertNull(cache.get(key));
}
@Test
public void put() {
String keyItem = name.getMethodName();
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(keyItem);
Object value = new Object();
assertNull(cache.get(key));
service.put(keyItem, value);
Cache.ValueWrapper result = cache.get(key);
assertNotNull(result);
assertEquals(value, result.get());
}
@Test
public void putWithException() {
String keyItem = name.getMethodName();
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(keyItem);
Object value = new Object();
assertNull(cache.get(key));
try {
service.putWithException(keyItem, value, true);
fail("Should have thrown an exception");
}
catch (UnsupportedOperationException e) {
// This is what we expect
}
Cache.ValueWrapper result = cache.get(key);
assertNotNull(result);
assertEquals(value, result.get());
}
@Test
public void putWithExceptionVetoPut() {
String keyItem = name.getMethodName();
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(keyItem);
Object value = new Object();
assertNull(cache.get(key));
try {
service.putWithException(keyItem, value, false);
fail("Should have thrown an exception");
}
catch (NullPointerException e) {
// This is what we expect
}
assertNull(cache.get(key));
}
@Test
public void earlyPut() {
String keyItem = name.getMethodName();
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(keyItem);
Object value = new Object();
assertNull(cache.get(key));
service.earlyPut(keyItem, value);
Cache.ValueWrapper result = cache.get(key);
assertNotNull(result);
assertEquals(value, result.get());
}
@Test
public void earlyPutWithException() {
String keyItem = name.getMethodName();
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(keyItem);
Object value = new Object();
assertNull(cache.get(key));
try {
service.earlyPutWithException(keyItem, value, true);
fail("Should have thrown an exception");
}
catch (UnsupportedOperationException e) {
// This is what we expect
}
Cache.ValueWrapper result = cache.get(key);
assertNotNull(result);
assertEquals(value, result.get());
}
@Test
public void earlyPutWithExceptionVetoPut() {
String keyItem = name.getMethodName();
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(keyItem);
Object value = new Object();
assertNull(cache.get(key));
try {
service.earlyPutWithException(keyItem, value, false);
fail("Should have thrown an exception");
}
catch (NullPointerException e) {
// This is what we expect
}
// This will be cached anyway as the earlyPut has updated the cache before
Cache.ValueWrapper result = cache.get(key);
assertNotNull(result);
assertEquals(value, result.get());
}
@Test
public void remove() {
String keyItem = name.getMethodName();
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(keyItem);
Object value = new Object();
cache.put(key, value);
service.remove(keyItem);
assertNull(cache.get(key));
}
@Test
public void removeWithException() {
String keyItem = name.getMethodName();
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(keyItem);
Object value = new Object();
cache.put(key, value);
try {
service.removeWithException(keyItem, true);
fail("Should have thrown an exception");
}
catch (UnsupportedOperationException e) {
// This is what we expect
}
assertNull(cache.get(key));
}
@Test
public void removeWithExceptionVetoRemove() {
String keyItem = name.getMethodName();
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(keyItem);
Object value = new Object();
cache.put(key, value);
try {
service.removeWithException(keyItem, false);
fail("Should have thrown an exception");
}
catch (NullPointerException e) {
// This is what we expect
}
Cache.ValueWrapper wrapper = cache.get(key);
assertNotNull(wrapper);
assertEquals(value, wrapper.get());
}
@Test
public void earlyRemove() {
String keyItem = name.getMethodName();
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(keyItem);
Object value = new Object();
cache.put(key, value);
service.earlyRemove(keyItem);
assertNull(cache.get(key));
}
@Test
public void earlyRemoveWithException() {
String keyItem = name.getMethodName();
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(keyItem);
Object value = new Object();
cache.put(key, value);
try {
service.earlyRemoveWithException(keyItem, true);
fail("Should have thrown an exception");
}
catch (UnsupportedOperationException e) {
// This is what we expect
}
assertNull(cache.get(key));
}
@Test
public void earlyRemoveWithExceptionVetoRemove() {
String keyItem = name.getMethodName();
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(keyItem);
Object value = new Object();
cache.put(key, value);
try {
service.earlyRemoveWithException(keyItem, false);
fail("Should have thrown an exception");
}
catch (NullPointerException e) {
// This is what we expect
}
// This will be remove anyway as the earlyRemove has removed the cache before
assertNull(cache.get(key));
}
@Test
public void removeAll() {
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(name.getMethodName());
cache.put(key, new Object());
service.removeAll();
assertTrue(isEmpty(cache));
}
@Test
public void removeAllWithException() {
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(name.getMethodName());
cache.put(key, new Object());
try {
service.removeAllWithException(true);
fail("Should have thrown an exception");
}
catch (UnsupportedOperationException e) {
// This is what we expect
}
assertTrue(isEmpty(cache));
}
@Test
public void removeAllWithExceptionVetoRemove() {
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(name.getMethodName());
cache.put(key, new Object());
try {
service.removeAllWithException(false);
fail("Should have thrown an exception");
}
catch (NullPointerException e) {
// This is what we expect
}
assertNotNull(cache.get(key));
}
@Test
public void earlyRemoveAll() {
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(name.getMethodName());
cache.put(key, new Object());
service.earlyRemoveAll();
assertTrue(isEmpty(cache));
}
@Test
public void earlyRemoveAllWithException() {
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(name.getMethodName());
cache.put(key, new Object());
try {
service.earlyRemoveAllWithException(true);
fail("Should have thrown an exception");
}
catch (UnsupportedOperationException e) {
// This is what we expect
}
assertTrue(isEmpty(cache));
}
@Test
public void earlyRemoveAllWithExceptionVetoRemove() {
Cache cache = getCache(DEFAULT_CACHE);
Object key = createKey(name.getMethodName());
cache.put(key, new Object());
try {
service.earlyRemoveAllWithException(false);
fail("Should have thrown an exception");
}
catch (NullPointerException e) {
// This is what we expect
}
// This will be remove anyway as the earlyRemove has removed the cache before
assertTrue(isEmpty(cache));
}
protected boolean isEmpty(Cache cache) {
ConcurrentHashMap<?, ?> nativeCache = (ConcurrentHashMap<?, ?>) cache.getNativeCache();
return nativeCache.isEmpty();
}
private Object createKey(Object... params) {
return SimpleKeyGenerator.generateKey(params);
}
private Cache getCache(String name) {
Cache cache = cacheManager.getCache(name);
assertNotNull("required cache " + name + " does not exist", cache);
return cache;
}
/**
* The only purpose of this method is to invoke a particular method on the
* service so that the call stack is different.
*/
private UnsupportedOperationException methodInCallStack(String keyItem) {
try {
service.cacheWithException(keyItem, true);
throw new IllegalStateException("Should have thrown an exception");
}
catch (UnsupportedOperationException e) {
return e;
}
}
private boolean contain(Throwable t, String className, String methodName) {
for (StackTraceElement element : t.getStackTrace()) {
if (className.equals(element.getClassName()) && methodName.equals(element.getMethodName())) {
return true;
}
}
return false;
}
}