diff --git a/spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCacheManager.java b/spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCacheManager.java index 2f83f5567b2..26092ee3ead 100644 --- a/spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCacheManager.java +++ b/spring-context-support/src/main/java/org/springframework/cache/guava/GuavaCacheManager.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. @@ -19,6 +19,7 @@ package org.springframework.cache.guava; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -29,6 +30,7 @@ import com.google.common.cache.CacheLoader; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.util.Assert; +import org.springframework.util.ObjectUtils; /** * {@link CacheManager} implementation that lazily builds {@link GuavaCache} @@ -45,6 +47,7 @@ import org.springframework.util.Assert; *

Requires Google Guava 12.0 or higher. * * @author Juergen Hoeller + * @author Stephane Nicoll * @since 4.0 * @see GuavaCache */ @@ -81,6 +84,8 @@ public class GuavaCacheManager implements CacheManager { * Specify the set of cache names for this CacheManager's 'static' mode. *

The number of caches and their names will be fixed after a call to this method, * with no creation of further cache regions at runtime. + *

Calling this with a {@code null} collection argument resets the + * mode to 'dynamic', allowing for further creation of caches again. */ public void setCacheNames(Collection cacheNames) { if (cacheNames != null) { @@ -89,6 +94,9 @@ public class GuavaCacheManager implements CacheManager { } this.dynamic = false; } + else { + this.dynamic = true; + } } /** @@ -99,7 +107,7 @@ public class GuavaCacheManager implements CacheManager { */ public void setCacheBuilder(CacheBuilder cacheBuilder) { Assert.notNull(cacheBuilder, "CacheBuilder must not be null"); - this.cacheBuilder = cacheBuilder; + doSetCacheBuilder(cacheBuilder); } /** @@ -109,7 +117,7 @@ public class GuavaCacheManager implements CacheManager { * @see com.google.common.cache.CacheBuilder#from(CacheBuilderSpec) */ public void setCacheBuilderSpec(CacheBuilderSpec cacheBuilderSpec) { - this.cacheBuilder = CacheBuilder.from(cacheBuilderSpec); + doSetCacheBuilder(CacheBuilder.from(cacheBuilderSpec)); } /** @@ -120,7 +128,7 @@ public class GuavaCacheManager implements CacheManager { * @see com.google.common.cache.CacheBuilder#from(String) */ public void setCacheSpecification(String cacheSpecification) { - this.cacheBuilder = CacheBuilder.from(cacheSpecification); + doSetCacheBuilder(CacheBuilder.from(cacheSpecification)); } /** @@ -131,7 +139,10 @@ public class GuavaCacheManager implements CacheManager { * @see com.google.common.cache.LoadingCache */ public void setCacheLoader(CacheLoader cacheLoader) { - this.cacheLoader = cacheLoader; + if (!ObjectUtils.nullSafeEquals(this.cacheLoader, cacheLoader)) { + this.cacheLoader = cacheLoader; + refreshKnownCaches(); + } } /** @@ -141,7 +152,10 @@ public class GuavaCacheManager implements CacheManager { * An internal holder object will be used to store user-level {@code null}s. */ public void setAllowNullValues(boolean allowNullValues) { - this.allowNullValues = allowNullValues; + if (this.allowNullValues != allowNullValues) { + this.allowNullValues = allowNullValues; + refreshKnownCaches(); + } } /** @@ -196,4 +210,20 @@ public class GuavaCacheManager implements CacheManager { } } + private void doSetCacheBuilder(CacheBuilder cacheBuilder) { + if (!ObjectUtils.nullSafeEquals(this.cacheBuilder, cacheBuilder)) { + this.cacheBuilder = cacheBuilder; + refreshKnownCaches(); + } + } + + /** + * Create the known caches again with the current state of this manager. + */ + private void refreshKnownCaches() { + for (Map.Entry entry : this.cacheMap.entrySet()) { + entry.setValue(createGuavaCache(entry.getKey())); + } + } + } diff --git a/spring-context-support/src/test/java/org/springframework/cache/guava/GuavaCacheManagerTests.java b/spring-context-support/src/test/java/org/springframework/cache/guava/GuavaCacheManagerTests.java index 0820d12f952..2a47a81a8d1 100644 --- a/spring-context-support/src/test/java/org/springframework/cache/guava/GuavaCacheManagerTests.java +++ b/spring-context-support/src/test/java/org/springframework/cache/guava/GuavaCacheManagerTests.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. @@ -16,15 +16,19 @@ package org.springframework.cache.guava; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; import org.junit.Test; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; /** * @author Juergen Hoeller + * @author Stephane Nicoll */ public class GuavaCacheManagerTests { @@ -56,7 +60,7 @@ public class GuavaCacheManagerTests { @Test public void testStaticMode() { - CacheManager cm = new GuavaCacheManager("c1", "c2"); + GuavaCacheManager cm = new GuavaCacheManager("c1", "c2"); Cache cache1 = cm.getCache("c1"); assertTrue(cache1 instanceof GuavaCache); Cache cache1again = cm.getCache("c1"); @@ -76,6 +80,79 @@ public class GuavaCacheManagerTests { assertNull(cache1.get("key3").get()); cache1.evict("key3"); assertNull(cache1.get("key3")); + + cm.setAllowNullValues(false); + Cache cache1x = cm.getCache("c1"); + assertTrue(cache1x instanceof GuavaCache); + assertTrue(cache1x != cache1); + Cache cache2x = cm.getCache("c2"); + assertTrue(cache2x instanceof GuavaCache); + assertTrue(cache2x != cache2); + Cache cache3x = cm.getCache("c3"); + assertNull(cache3x); + + cache1x.put("key1", "value1"); + assertEquals("value1", cache1x.get("key1").get()); + cache1x.put("key2", 2); + assertEquals(2, cache1x.get("key2").get()); + try { + cache1x.put("key3", null); + fail("Should have thrown NullPointerException"); + } + catch (NullPointerException ex) { + // expected + } + + cm.setAllowNullValues(true); + Cache cache1y = cm.getCache("c1"); + + cache1y.put("key3", null); + assertNull(cache1y.get("key3").get()); + cache1y.evict("key3"); + assertNull(cache1y.get("key3")); + } + + @Test + public void changeCacheSpecificationRecreateCache() { + GuavaCacheManager cm = new GuavaCacheManager("c1"); + Cache cache1 = cm.getCache("c1"); + + CacheBuilder cacheBuilder = CacheBuilder.newBuilder().maximumSize(10); + cm.setCacheBuilder(cacheBuilder); + Cache cache1x = cm.getCache("c1"); + assertTrue(cache1x != cache1); + + cm.setCacheBuilder(cacheBuilder); // Set same instance + Cache cache1xx = cm.getCache("c1"); + assertSame(cache1x, cache1xx); + } + + @Test + public void changeCacheLoaderRecreateCache() { + GuavaCacheManager cm = new GuavaCacheManager("c1"); + Cache cache1 = cm.getCache("c1"); + + CacheLoader loader = mockCacheLoader(); + cm.setCacheLoader(loader); + Cache cache1x = cm.getCache("c1"); + assertTrue(cache1x != cache1); + + cm.setCacheLoader(loader); // Set same instance + Cache cache1xx = cm.getCache("c1"); + assertSame(cache1x, cache1xx); + } + + @Test + public void setCacheNameNullRestoreDynamicMode() { + GuavaCacheManager cm = new GuavaCacheManager("c1"); + assertNull(cm.getCache("someCache")); + cm.setCacheNames(null); + assertNotNull(cm.getCache("someCache")); + } + + @SuppressWarnings("unchecked") + private CacheLoader mockCacheLoader() { + return mock(CacheLoader.class); } }