From 78484129f5e7ec4d384fbd34a55fc4f28ba497a8 Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 24 Jul 2014 15:06:36 +0200 Subject: [PATCH] ConcurrentMapCacheManager recreates caches in case of setAllowNullValues change Issue: SPR-12026 --- .../concurrent/ConcurrentMapCacheManager.java | 18 ++++++++-- .../ConcurrentMapCacheManagerTests.java | 34 +++++++++++++++++-- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java index 5f69a828e5..14c6956bf5 100644 --- a/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.java +++ b/spring-context/src/main/java/org/springframework/cache/concurrent/ConcurrentMapCacheManager.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.concurrent; 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; @@ -70,6 +71,8 @@ public class ConcurrentMapCacheManager 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) { @@ -78,6 +81,9 @@ public class ConcurrentMapCacheManager implements CacheManager { } this.dynamic = false; } + else { + this.dynamic = true; + } } /** @@ -85,9 +91,17 @@ public class ConcurrentMapCacheManager implements CacheManager { * in this cache manager. *

Default is "true", despite ConcurrentHashMap itself not supporting {@code null} * values. An internal holder object will be used to store user-level {@code null}s. + *

Note: A change of the null-value setting will reset all existing caches, + * if any, to reconfigure them with the new null-value requirement. */ public void setAllowNullValues(boolean allowNullValues) { - this.allowNullValues = allowNullValues; + if (allowNullValues != this.allowNullValues) { + this.allowNullValues = allowNullValues; + // Need to recreate all Cache instances with the new null-value configuration... + for (Map.Entry entry : this.cacheMap.entrySet()) { + entry.setValue(createConcurrentMapCache(entry.getKey())); + } + } } /** diff --git a/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java b/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java index 661e4a3d35..7e19619a7d 100644 --- a/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java +++ b/spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.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. @@ -56,7 +56,7 @@ public class ConcurrentMapCacheManagerTests { @Test public void testStaticMode() { - CacheManager cm = new ConcurrentMapCacheManager("c1", "c2"); + ConcurrentMapCacheManager cm = new ConcurrentMapCacheManager("c1", "c2"); Cache cache1 = cm.getCache("c1"); assertTrue(cache1 instanceof ConcurrentMapCache); Cache cache1again = cm.getCache("c1"); @@ -76,6 +76,36 @@ public class ConcurrentMapCacheManagerTests { assertNull(cache1.get("key3").get()); cache1.evict("key3"); assertNull(cache1.get("key3")); + + cm.setAllowNullValues(false); + Cache cache1x = cm.getCache("c1"); + assertTrue(cache1x instanceof ConcurrentMapCache); + assertTrue(cache1x != cache1); + Cache cache2x = cm.getCache("c2"); + assertTrue(cache2x instanceof ConcurrentMapCache); + 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")); } }