Skip to content

Commit fd568f3

Browse files
committed
Improve allowNullValue handling when a null value is provided
This commit improves `AbstractValueAdaptingCache` to throw a dedicated exception if `allowNullValues` is `false` and a `null` value is provided anyway. This avoid a lower-level exception from the cache library that will miss some context. Issue: SPR-15173
1 parent 45b27a8 commit fd568f3

File tree

9 files changed

+118
-52
lines changed

9 files changed

+118
-52
lines changed

spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheManagerTests.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -102,14 +102,7 @@ public void testStaticMode() {
102102
assertEquals("value1", cache1x.get("key1").get());
103103
cache1x.put("key2", 2);
104104
assertEquals(2, cache1x.get("key2").get());
105-
try {
106-
cache1x.put("key3", null);
107-
fail("Should have thrown NullPointerException");
108-
}
109-
catch (NullPointerException ex) {
110-
// expected
111-
}
112-
105+
113106
cm.setAllowNullValues(true);
114107
Cache cache1y = cm.getCache("c1");
115108

spring-context-support/src/test/java/org/springframework/cache/caffeine/CaffeineCacheTests.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
2020
import org.junit.Before;
2121
import org.junit.Test;
2222

23-
import org.springframework.cache.AbstractCacheTests;
23+
import org.springframework.cache.AbstractValueAdaptingCacheTests;
2424
import org.springframework.cache.Cache;
2525

2626
import static org.junit.Assert.*;
@@ -29,21 +29,31 @@
2929
* @author Ben Manes
3030
* @author Stephane Nicoll
3131
*/
32-
public class CaffeineCacheTests extends AbstractCacheTests<CaffeineCache> {
32+
public class CaffeineCacheTests extends AbstractValueAdaptingCacheTests<CaffeineCache> {
3333

3434
private com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCache;
3535

3636
private CaffeineCache cache;
3737

38+
private CaffeineCache cacheNoNull;
39+
3840
@Before
3941
public void setUp() {
4042
nativeCache = Caffeine.newBuilder().build();
4143
cache = new CaffeineCache(CACHE_NAME, nativeCache);
44+
com.github.benmanes.caffeine.cache.Cache<Object, Object> nativeCacheNoNull
45+
= Caffeine.newBuilder().build();
46+
cacheNoNull = new CaffeineCache(CACHE_NAME_NO_NULL, nativeCacheNoNull, false);
4247
}
4348

4449
@Override
4550
protected CaffeineCache getCache() {
46-
return cache;
51+
return getCache(true);
52+
}
53+
54+
@Override
55+
protected CaffeineCache getCache(boolean allowNull) {
56+
return allowNull ? this.cache : this.cacheNoNull;
4757
}
4858

4959
@Override

spring-context-support/src/test/java/org/springframework/cache/guava/GuavaCacheManagerTests.java

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,7 @@ public void testStaticMode() {
101101
assertEquals("value1", cache1x.get("key1").get());
102102
cache1x.put("key2", 2);
103103
assertEquals(2, cache1x.get("key2").get());
104-
try {
105-
cache1x.put("key3", null);
106-
fail("Should have thrown NullPointerException");
107-
}
108-
catch (NullPointerException ex) {
109-
// expected
110-
}
111-
104+
112105
cm.setAllowNullValues(true);
113106
Cache cache1y = cm.getCache("c1");
114107

spring-context-support/src/test/java/org/springframework/cache/jcache/JCacheEhCacheApiTests.java

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,26 +25,32 @@
2525
import org.junit.After;
2626
import org.junit.Before;
2727

28-
import org.springframework.cache.AbstractCacheTests;
28+
import org.springframework.cache.AbstractValueAdaptingCacheTests;
2929

3030
/**
3131
* @author Stephane Nicoll
3232
*/
33-
public class JCacheEhCacheApiTests extends AbstractCacheTests<JCacheCache> {
33+
public class JCacheEhCacheApiTests extends AbstractValueAdaptingCacheTests<JCacheCache> {
3434

3535
private CacheManager cacheManager;
3636

3737
private Cache<Object, Object> nativeCache;
3838

3939
private JCacheCache cache;
4040

41+
private JCacheCache cacheNoNull;
42+
4143

4244
@Before
4345
public void setup() {
4446
this.cacheManager = getCachingProvider().getCacheManager();
4547
this.cacheManager.createCache(CACHE_NAME, new MutableConfiguration<>());
48+
this.cacheManager.createCache(CACHE_NAME_NO_NULL, new MutableConfiguration<>());
4649
this.nativeCache = this.cacheManager.getCache(CACHE_NAME);
4750
this.cache = new JCacheCache(this.nativeCache);
51+
Cache<Object, Object> nativeCacheNoNull =
52+
this.cacheManager.getCache(CACHE_NAME_NO_NULL);
53+
this.cacheNoNull = new JCacheCache(nativeCacheNoNull, false);
4854
}
4955

5056
protected CachingProvider getCachingProvider() {
@@ -58,10 +64,14 @@ public void shutdown() {
5864
}
5965
}
6066

61-
6267
@Override
6368
protected JCacheCache getCache() {
64-
return this.cache;
69+
return getCache(true);
70+
}
71+
72+
@Override
73+
protected JCacheCache getCache(boolean allowNull) {
74+
return allowNull ? this.cache : this.cacheNoNull;
6575
}
6676

6777
@Override

spring-context/src/main/java/org/springframework/cache/support/AbstractValueAdaptingCache.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -95,8 +95,13 @@ protected Object fromStoreValue(Object storeValue) {
9595
* @return the value to store
9696
*/
9797
protected Object toStoreValue(Object userValue) {
98-
if (this.allowNullValues && userValue == null) {
99-
return NullValue.INSTANCE;
98+
if (userValue == null) {
99+
if (this.allowNullValues) {
100+
return NullValue.INSTANCE;
101+
}
102+
throw new IllegalArgumentException(
103+
String.format("Cache '%s' is configured to not allow null " +
104+
"values but null was provided", getName()));
100105
}
101106
return userValue;
102107
}

spring-context/src/test/java/org/springframework/cache/AbstractCacheTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2002-2017 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cache;
18+
19+
import org.junit.Rule;
20+
import org.junit.Test;
21+
import org.junit.rules.ExpectedException;
22+
23+
import org.springframework.cache.support.AbstractValueAdaptingCache;
24+
25+
/**
26+
* @author Stephane Nicoll
27+
*/
28+
public abstract class AbstractValueAdaptingCacheTests<T extends AbstractValueAdaptingCache>
29+
extends AbstractCacheTests<T> {
30+
31+
@Rule
32+
public ExpectedException thrown = ExpectedException.none();
33+
34+
protected final static String CACHE_NAME_NO_NULL = "testCacheNoNull";
35+
36+
protected abstract T getCache(boolean allowNull);
37+
38+
@Test
39+
public void testCachePutNullValueAllowNullFalse() {
40+
T cache = getCache(false);
41+
String key = createRandomKey();
42+
43+
this.thrown.expect(IllegalArgumentException.class);
44+
this.thrown.expectMessage(CACHE_NAME_NO_NULL);
45+
this.thrown.expectMessage(
46+
"is configured to not allow null values but null was provided");
47+
cache.put(key, null);
48+
}
49+
50+
}

spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheManagerTests.java

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -104,14 +104,7 @@ public void testStaticMode() {
104104
assertEquals("value1", cache1x.get("key1").get());
105105
cache1x.put("key2", 2);
106106
assertEquals(2, cache1x.get("key2").get());
107-
try {
108-
cache1x.put("key3", null);
109-
fail("Should have thrown NullPointerException");
110-
}
111-
catch (NullPointerException ex) {
112-
// expected
113-
}
114-
107+
115108
cm.setAllowNullValues(true);
116109
Cache cache1y = cm.getCache("c1");
117110

spring-context/src/test/java/org/springframework/cache/concurrent/ConcurrentMapCacheTests.java

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -25,7 +25,7 @@
2525
import org.junit.Before;
2626
import org.junit.Test;
2727

28-
import org.springframework.cache.AbstractCacheTests;
28+
import org.springframework.cache.AbstractValueAdaptingCacheTests;
2929
import org.springframework.core.serializer.support.SerializationDelegate;
3030

3131
import static org.junit.Assert.*;
@@ -35,23 +35,35 @@
3535
* @author Juergen Hoeller
3636
* @author Stephane Nicoll
3737
*/
38-
public class ConcurrentMapCacheTests extends AbstractCacheTests<ConcurrentMapCache> {
38+
public class ConcurrentMapCacheTests
39+
extends AbstractValueAdaptingCacheTests<ConcurrentMapCache> {
3940

4041
protected ConcurrentMap<Object, Object> nativeCache;
4142

4243
protected ConcurrentMapCache cache;
4344

45+
protected ConcurrentMap<Object, Object> nativeCacheNoNull;
46+
47+
protected ConcurrentMapCache cacheNoNull;
48+
4449

4550
@Before
4651
public void setUp() throws Exception {
47-
nativeCache = new ConcurrentHashMap<Object, Object>();
48-
cache = new ConcurrentMapCache(CACHE_NAME, nativeCache, true);
49-
cache.clear();
52+
this.nativeCache = new ConcurrentHashMap<Object, Object>();
53+
this.cache = new ConcurrentMapCache(CACHE_NAME, this.nativeCache, true);
54+
this.nativeCacheNoNull = new ConcurrentHashMap<Object, Object>();
55+
this.cacheNoNull = new ConcurrentMapCache(CACHE_NAME_NO_NULL,
56+
this.nativeCacheNoNull, false);
57+
this.cache.clear();
5058
}
5159

5260
@Override
5361
protected ConcurrentMapCache getCache() {
54-
return this.cache;
62+
return getCache(true);
63+
}
64+
65+
@Override protected ConcurrentMapCache getCache(boolean allowNull) {
66+
return allowNull ? this.cache : this.cacheNoNull;
5567
}
5668

5769
@Override
@@ -84,9 +96,9 @@ public void testSerializer() {
8496
public void testNonSerializableContent() {
8597
ConcurrentMapCache serializeCache = createCacheWithStoreByValue();
8698

87-
thrown.expect(IllegalArgumentException.class);
88-
thrown.expectMessage("Failed to serialize");
89-
thrown.expectMessage(this.cache.getClass().getName());
99+
this.thrown.expect(IllegalArgumentException.class);
100+
this.thrown.expectMessage("Failed to serialize");
101+
this.thrown.expectMessage(this.cache.getClass().getName());
90102
serializeCache.put(createRandomKey(), this.cache);
91103
}
92104

@@ -96,15 +108,15 @@ public void testInvalidSerializedContent() {
96108

97109
String key = createRandomKey();
98110
this.nativeCache.put(key, "Some garbage");
99-
thrown.expect(IllegalArgumentException.class);
100-
thrown.expectMessage("Failed to deserialize");
101-
thrown.expectMessage("Some garbage");
111+
this.thrown.expect(IllegalArgumentException.class);
112+
this.thrown.expectMessage("Failed to deserialize");
113+
this.thrown.expectMessage("Some garbage");
102114
serializeCache.get(key);
103115
}
104116

105117

106118
private ConcurrentMapCache createCacheWithStoreByValue() {
107-
return new ConcurrentMapCache(CACHE_NAME, nativeCache, true,
119+
return new ConcurrentMapCache(CACHE_NAME, this.nativeCache, true,
108120
new SerializationDelegate(ConcurrentMapCacheTests.class.getClassLoader()));
109121
}
110122

0 commit comments

Comments
 (0)