From 119dfd9cf9215b2800d2d178913917103761fefd Mon Sep 17 00:00:00 2001 From: Stephane Nicoll Date: Tue, 11 Feb 2014 11:48:47 +0100 Subject: [PATCH] Fix cache decoration Prior to this commit, a cache that is added on-the-fly is not properly decorated by the provided CacheManager implementation that supports it (EhCache and JCache). This commits adds an extra getMissingCache method to the AbstractCacheManager that can be extended to provide a cache that may exist in the native cache manager but is not yet known by the spring abstraction. Issue: SPR-11518 --- .../cache/ehcache/EhCacheCacheManager.java | 18 ++- .../cache/jcache/JCacheCacheManager.java | 17 ++- .../ehcache/EhCacheCacheManagerTests.java | 79 ++++++++++++ .../cache/jcache/JCacheCacheManagerTests.java | 106 ++++++++++++++++ ...ransactionSupportingCacheManagerTests.java | 115 ++++++++++++++++++ .../cache/support/AbstractCacheManager.java | 34 +++++- 6 files changed, 347 insertions(+), 22 deletions(-) create mode 100644 spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheCacheManagerTests.java create mode 100644 spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheCacheManagerTests.java create mode 100644 spring-context-support/src/test/java/org/springframework/cache/transaction/AbstractTransactionSupportingCacheManagerTests.java diff --git a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCacheManager.java b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCacheManager.java index 212f5d6e0dd..ec7dabdddce 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCacheManager.java +++ b/spring-context-support/src/main/java/org/springframework/cache/ehcache/EhCacheCacheManager.java @@ -31,6 +31,7 @@ import org.springframework.util.Assert; * * @author Costin Leau * @author Juergen Hoeller + * @author Stephane Nicoll * @since 3.1 */ public class EhCacheCacheManager extends AbstractTransactionSupportingCacheManager { @@ -86,17 +87,14 @@ public class EhCacheCacheManager extends AbstractTransactionSupportingCacheManag } @Override - public Cache getCache(String name) { - Cache cache = super.getCache(name); - if (cache == null) { - // Check the EhCache cache again (in case the cache was added at runtime) - Ehcache ehcache = getCacheManager().getEhcache(name); - if (ehcache != null) { - addCache(new EhCacheCache(ehcache)); - cache = super.getCache(name); // potentially decorated - } + protected Cache getMissingCache(String name) { + // check the EhCache cache again + // (in case the cache was added at runtime) + Ehcache ehcache = getCacheManager().getEhcache(name); + if (ehcache != null) { + return new EhCacheCache(ehcache); } - return cache; + return null; } } diff --git a/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCacheManager.java b/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCacheManager.java index 453f87828b0..9c324efcddf 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCacheManager.java +++ b/spring-context-support/src/main/java/org/springframework/cache/jcache/JCacheCacheManager.java @@ -31,6 +31,7 @@ import org.springframework.cache.transaction.AbstractTransactionSupportingCacheM *

Note: This class has been updated for JCache 1.0, as of Spring 4.0. * * @author Juergen Hoeller + * @author Stephane Nicoll * @since 3.2 */ public class JCacheCacheManager extends AbstractTransactionSupportingCacheManager { @@ -108,17 +109,13 @@ public class JCacheCacheManager extends AbstractTransactionSupportingCacheManage } @Override - public Cache getCache(String name) { - Cache cache = super.getCache(name); - if (cache == null) { - // Check the JCache cache again (in case the cache was added at runtime) - javax.cache.Cache jcache = getCacheManager().getCache(name); - if (jcache != null) { - addCache(new JCacheCache(jcache, isAllowNullValues())); - cache = super.getCache(name); // potentially decorated - } + protected Cache getMissingCache(String name) { + // Check the JCache cache again (in case the cache was added at runtime) + javax.cache.Cache jcache = getCacheManager().getCache(name); + if (jcache != null) { + return new JCacheCache(jcache, isAllowNullValues()); } - return cache; + return null; } } diff --git a/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheCacheManagerTests.java b/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheCacheManagerTests.java new file mode 100644 index 00000000000..eb75558d454 --- /dev/null +++ b/spring-context-support/src/test/java/org/springframework/cache/ehcache/EhCacheCacheManagerTests.java @@ -0,0 +1,79 @@ +/* + * Copyright 2002-2014 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.ehcache; + +import net.sf.ehcache.CacheManager; +import net.sf.ehcache.config.CacheConfiguration; +import net.sf.ehcache.config.Configuration; +import org.junit.After; +import org.junit.Before; +import org.springframework.cache.transaction.AbstractTransactionSupportingCacheManagerTests; + +/** + * @author Stephane Nicoll + */ +public class EhCacheCacheManagerTests extends AbstractTransactionSupportingCacheManagerTests { + + private CacheManager nativeCacheManager; + private EhCacheCacheManager cacheManager; + private EhCacheCacheManager transactionalCacheManager; + + @Before + public void setup() { + nativeCacheManager = new CacheManager(new Configuration().name("EhCacheCacheManagerTests") + .defaultCache(new CacheConfiguration("default", 100))); + addNativeCache(CACHE_NAME); + + cacheManager = new EhCacheCacheManager(nativeCacheManager); + cacheManager.setTransactionAware(false); + cacheManager.afterPropertiesSet(); + + transactionalCacheManager = new EhCacheCacheManager(nativeCacheManager); + transactionalCacheManager.setTransactionAware(true); + transactionalCacheManager.afterPropertiesSet(); + } + + @After + public void tearDown() { + nativeCacheManager.shutdown(); + } + + + @Override + protected EhCacheCacheManager getCacheManager(boolean transactionAware) { + if (transactionAware) { + return transactionalCacheManager; + } else { + return cacheManager; + } + } + + @Override + protected Class getCacheType() { + return EhCacheCache.class; + } + + @Override + protected void addNativeCache(String cacheName) { + nativeCacheManager.addCache(cacheName); + } + + @Override + protected void removeNativeCache(String cacheName) { + nativeCacheManager.removeCache(cacheName); + } +} diff --git a/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheCacheManagerTests.java b/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheCacheManagerTests.java new file mode 100644 index 00000000000..c60798b88c9 --- /dev/null +++ b/spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheCacheManagerTests.java @@ -0,0 +1,106 @@ +/* + * Copyright 2002-2014 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; + +import static org.mockito.BDDMockito.*; +import static org.mockito.Mockito.mock; + +import javax.cache.Cache; +import javax.cache.CacheManager; +import java.util.ArrayList; +import java.util.List; + +import org.junit.Before; +import org.springframework.cache.transaction.AbstractTransactionSupportingCacheManagerTests; + +/** + * @author Stephane Nicoll + */ +public class JCacheCacheManagerTests extends AbstractTransactionSupportingCacheManagerTests { + + private CacheManagerMock cacheManagerMock; + private JCacheCacheManager cacheManager; + private JCacheCacheManager transactionalCacheManager; + + @Before + public void setupOnce() { + cacheManagerMock = new CacheManagerMock(); + cacheManagerMock.addCache(CACHE_NAME); + + cacheManager = new JCacheCacheManager(cacheManagerMock.getCacheManager()); + cacheManager.setTransactionAware(false); + cacheManager.afterPropertiesSet(); + + transactionalCacheManager = new JCacheCacheManager(cacheManagerMock.getCacheManager()); + transactionalCacheManager.setTransactionAware(true); + transactionalCacheManager.afterPropertiesSet(); + } + + + @Override + protected JCacheCacheManager getCacheManager(boolean transactionAware) { + if (transactionAware) { + return transactionalCacheManager; + } else { + return cacheManager; + } + } + + @Override + protected Class getCacheType() { + return JCacheCache.class; + } + + @Override + protected void addNativeCache(String cacheName) { + cacheManagerMock.addCache(cacheName); + } + + @Override + protected void removeNativeCache(String cacheName) { + cacheManagerMock.removeCache(cacheName); + } + + private static class CacheManagerMock { + + private final List cacheNames; + private final CacheManager cacheManager; + + private CacheManagerMock() { + this.cacheNames = new ArrayList(); + this.cacheManager = mock(CacheManager.class); + given(cacheManager.getCacheNames()).willReturn(cacheNames); + } + + private CacheManager getCacheManager() { + return cacheManager; + } + + @SuppressWarnings("unchecked") + public void addCache(String name) { + cacheNames.add(name); + Cache cache = mock(Cache.class); + given(cache.getName()).willReturn(name); + given(cacheManager.getCache(name)).willReturn(cache); + } + + public void removeCache(String name) { + cacheNames.remove(name); + given(cacheManager.getCache(name)).willReturn(null); + } + } +} diff --git a/spring-context-support/src/test/java/org/springframework/cache/transaction/AbstractTransactionSupportingCacheManagerTests.java b/spring-context-support/src/test/java/org/springframework/cache/transaction/AbstractTransactionSupportingCacheManagerTests.java new file mode 100644 index 00000000000..39fb0db58d4 --- /dev/null +++ b/spring-context-support/src/test/java/org/springframework/cache/transaction/AbstractTransactionSupportingCacheManagerTests.java @@ -0,0 +1,115 @@ +/* + * Copyright 2002-2014 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.transaction; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.springframework.cache.Cache; +import org.springframework.cache.CacheManager; + +/** + * Shared tests for {@link CacheManager} that inherit from + * {@link AbstractTransactionSupportingCacheManager}. + * + * @author Stephane Nicoll + */ +public abstract class AbstractTransactionSupportingCacheManagerTests { + + public static final String CACHE_NAME = "testCacheManager"; + + @Rule + public final TestName name = new TestName(); + + + /** + * Returns the {@link CacheManager} to use. + * + * @param transactionAware if the requested cache manager should be aware + * of the transaction + * @return the cache manager to use + * @see org.springframework.cache.transaction.AbstractTransactionSupportingCacheManager#setTransactionAware(boolean) + */ + protected abstract T getCacheManager(boolean transactionAware); + + /** + * Returns the expected concrete type of the cache. + */ + protected abstract Class getCacheType(); + + /** + * Adds a cache with the specified name to the native manager. + */ + protected abstract void addNativeCache(String cacheName); + + /** + * Removes the cache with the specified name from the native manager. + */ + protected abstract void removeNativeCache(String cacheName); + + @Test + public void getOnExistingCache() { + assertThat(getCacheManager(false).getCache(CACHE_NAME), is(instanceOf(getCacheType()))); + } + + @Test + public void getOnNewCache() { + T cacheManager = getCacheManager(false); + String cacheName = name.getMethodName(); + addNativeCache(cacheName); + assertFalse(cacheManager.getCacheNames().contains(cacheName)); + try { + assertThat(cacheManager.getCache(cacheName), + is(instanceOf(getCacheType()))); + assertTrue(cacheManager.getCacheNames().contains(cacheName)); + } finally { + removeNativeCache(cacheName); + } + } + + @Test + public void getOnUnknownCache() { + T cacheManager = getCacheManager(false); + String cacheName = name.getMethodName(); + assertFalse(cacheManager.getCacheNames().contains(cacheName)); + assertThat(cacheManager.getCache(cacheName), nullValue()); + } + + @Test + public void getTransactionalOnExistingCache() { + assertThat(getCacheManager(true).getCache(CACHE_NAME), + is(instanceOf(TransactionAwareCacheDecorator.class))); + } + + @Test + public void getTransactionalOnNewCache() { + String cacheName = name.getMethodName(); + T cacheManager = getCacheManager(true); + assertFalse(cacheManager.getCacheNames().contains(cacheName)); + addNativeCache(cacheName); + try { + assertThat(cacheManager.getCache(cacheName), + is(instanceOf(TransactionAwareCacheDecorator.class))); + assertTrue(cacheManager.getCacheNames().contains(cacheName)); + } finally { + removeNativeCache(cacheName); + } + } +} diff --git a/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java b/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java index 364472409b4..677f7fe3ac3 100644 --- a/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java +++ b/spring-context/src/main/java/org/springframework/cache/support/AbstractCacheManager.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 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. @@ -33,6 +33,7 @@ import org.springframework.cache.CacheManager; * * @author Costin Leau * @author Juergen Hoeller + * @author Stephane Nicoll * @since 3.1 */ public abstract class AbstractCacheManager implements CacheManager, InitializingBean { @@ -70,10 +71,36 @@ public abstract class AbstractCacheManager implements CacheManager, Initializing return cache; } + /** + * Return a missing cache with the specified {@code name} or {@code null} if + * such cache does not exist or could not be created on the fly. + *

Some caches may be created at runtime in the native provided. If a lookup + * by name does not yield any result, a subclass gets a chance to register + * such a cache at runtime. The returned cache will be automatically added to + * this instance. + * @param name the name of the cache to retrieve + * @return the missing cache or {@code null} if no such cache exists or could be + * created + * @see #getCache(String) + */ + protected Cache getMissingCache(String name) { + return null; + } @Override public Cache getCache(String name) { - return this.cacheMap.get(name); + Cache cache = lookupCache(name); + if (cache != null) { + return cache; + } + else { + Cache missingCache = getMissingCache(name); + if (missingCache != null) { + addCache(missingCache); + return lookupCache(name); // May be decorated + } + return null; + } } @Override @@ -81,6 +108,9 @@ public abstract class AbstractCacheManager implements CacheManager, Initializing return Collections.unmodifiableSet(this.cacheNames); } + private Cache lookupCache(String name) { + return this.cacheMap.get(name); + } /** * Load the caches for this cache manager. Occurs at startup.