/* * 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; } }