SGF-327 - Avoid setting null values with GemFire's Cache Region put(key, value) operation when GemFire is used as the caching provider in Spring's Cache Abstraction (@Cacheable).
This commit is contained in:
@@ -71,11 +71,11 @@ public class GemfireCache implements Cache {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(final Object key, final Class<T> type) {
|
||||
|
||||
Object value = region.get(key);
|
||||
|
||||
if (value != null && type != null && !type.isInstance(value)) {
|
||||
throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value);
|
||||
throw new IllegalStateException(String.format("Cached value is not of required type [%1$s]: %2$s",
|
||||
type.getName(), value));
|
||||
}
|
||||
|
||||
return (T) value;
|
||||
@@ -83,7 +83,9 @@ public class GemfireCache implements Cache {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void put(final Object key, final Object value) {
|
||||
region.put(key, value);
|
||||
if (value != null) {
|
||||
region.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,8 +96,9 @@ public class GemfireCache implements Cache {
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public ValueWrapper putIfAbsent(Object key, Object value) {
|
||||
|
||||
Object existingValue = region.putIfAbsent(key, value);
|
||||
return existingValue == null ? null : new SimpleValueWrapper(existingValue);
|
||||
|
||||
return (existingValue == null ? null : new SimpleValueWrapper(existingValue));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -84,8 +84,7 @@ public class GemfireCacheManager extends AbstractCacheManager {
|
||||
Cache cache = super.getCache(name);
|
||||
|
||||
if (cache == null) {
|
||||
// check the gemfire cache again
|
||||
// in case the cache was added at runtime
|
||||
// check the GemFire Cache again in case the Cache (Region) was added at runtime
|
||||
Region<?, ?> region = gemfireCache.getRegion(name);
|
||||
|
||||
if (region != null) {
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright 2010-2013 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.data.gemfire.support;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.Map;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.gemstone.gemfire.cache.Region;
|
||||
|
||||
/**
|
||||
* The CachingWithGemFireIntegrationTest class is a test suite of test cases testing the contract and functionality
|
||||
* of Spring Framework's Cache Abstraction using GemFire as a caching provider applied with Spring Data GemFire.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.junit.runner.RunWith
|
||||
* @see org.springframework.cache.annotation.Cacheable
|
||||
* @see org.springframework.test.context.ActiveProfiles
|
||||
* @see org.springframework.test.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
|
||||
* @since 1.5.1
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration
|
||||
@ActiveProfiles("replica")
|
||||
@SuppressWarnings("unused")
|
||||
public class CachingWithGemFireIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private NamedNumbersService namedNumbersService;
|
||||
|
||||
@Resource(name = "NamedNumbersRegion")
|
||||
private Region<String, Integer> namedNumbersRegion;
|
||||
|
||||
@Test(expected = NullPointerException.class)
|
||||
public void testRegionCacheHit() {
|
||||
assertNull(namedNumbersRegion.get("eleven"));
|
||||
assertFalse(namedNumbersRegion.containsKey("eleven"));
|
||||
|
||||
namedNumbersRegion.put("eleven", 11);
|
||||
|
||||
assertTrue(namedNumbersRegion.containsKey("eleven"));
|
||||
assertEquals(11, namedNumbersService.get("eleven").intValue());
|
||||
assertFalse(namedNumbersService.wasCacheMiss());
|
||||
|
||||
try {
|
||||
namedNumbersRegion.put("eleven", null); // GemFire does not accept null values on put(key, value)
|
||||
}
|
||||
finally {
|
||||
assertTrue(namedNumbersRegion.containsKey("eleven"));
|
||||
assertEquals(11, namedNumbersRegion.get("eleven").intValue());
|
||||
assertEquals(11, namedNumbersService.get("eleven").intValue());
|
||||
assertFalse(namedNumbersService.wasCacheMiss());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegionCaching() {
|
||||
assertFalse(namedNumbersService.wasCacheMiss());
|
||||
assertEquals(1, namedNumbersService.get("one").intValue());
|
||||
assertTrue(namedNumbersService.wasCacheMiss());
|
||||
assertEquals(1, namedNumbersService.get("one").intValue());
|
||||
assertFalse(namedNumbersService.wasCacheMiss());
|
||||
assertEquals(2, namedNumbersService.get("two").intValue());
|
||||
assertTrue(namedNumbersService.wasCacheMiss());
|
||||
assertEquals(2, namedNumbersService.get("two").intValue());
|
||||
assertFalse(namedNumbersService.wasCacheMiss());
|
||||
assertNull(namedNumbersService.get("twelve"));
|
||||
assertTrue(namedNumbersService.wasCacheMiss());
|
||||
assertNull(namedNumbersService.get("twelve"));
|
||||
assertTrue(namedNumbersService.wasCacheMiss());
|
||||
}
|
||||
|
||||
public static class NamedNumbersService {
|
||||
|
||||
private NamedNumbersInMemoryRepository namedNumbersRepo;
|
||||
|
||||
public final void setNamedNumbersRepo(final NamedNumbersInMemoryRepository namedNumbersRepo) {
|
||||
Assert.notNull(namedNumbersRepo, "The 'NamedNumbers' Repository must not be null!");
|
||||
this.namedNumbersRepo = namedNumbersRepo;
|
||||
}
|
||||
|
||||
protected NamedNumbersInMemoryRepository getNamedNumbersRepo() {
|
||||
Assert.state(namedNumbersRepo != null,
|
||||
"A reference to the 'NamedNumbers' Repository was not properly configured and initialized!");
|
||||
return namedNumbersRepo;
|
||||
}
|
||||
|
||||
@Cacheable("NamedNumbersRegion")
|
||||
public Integer get(final String namedNumber) {
|
||||
return getNamedNumbersRepo().get(namedNumber);
|
||||
}
|
||||
|
||||
public boolean wasCacheMiss() {
|
||||
return getNamedNumbersRepo().wasCacheMiss();
|
||||
}
|
||||
}
|
||||
|
||||
public static class NamedNumbersInMemoryRepository {
|
||||
|
||||
private volatile boolean cacheMiss;
|
||||
|
||||
private Map<String, Integer> namedNumbers;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
getNamedNumbers();
|
||||
}
|
||||
|
||||
public final void setNamedNumbers(final Map<String, Integer> namedNumbers) {
|
||||
Assert.notNull(namedNumbers, "The reference to the 'NamedNumbers' Map must not be null!");
|
||||
this.namedNumbers = namedNumbers;
|
||||
}
|
||||
|
||||
protected Map<String, Integer> getNamedNumbers() {
|
||||
Assert.state(namedNumbers != null, "The 'NamedNumbers' Map was not properly configured and initialized!");
|
||||
return namedNumbers;
|
||||
}
|
||||
|
||||
public Integer get(final String namedNumber) {
|
||||
this.cacheMiss = true;
|
||||
return namedNumbers.get(namedNumber);
|
||||
}
|
||||
|
||||
public boolean wasCacheMiss() {
|
||||
boolean localCacheMiss = this.cacheMiss;
|
||||
this.cacheMiss = false;
|
||||
return localCacheMiss;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:cache="http://www.springframework.org/schema/cache"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xmlns:gfe="http://www.springframework.org/schema/gemfire"
|
||||
xmlns:p="http://www.springframework.org/schema/p"
|
||||
xmlns:util="http://www.springframework.org/schema/util"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
|
||||
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
|
||||
http://www.springframework.org/schema/gemfire http://www.springframework.org/schema/gemfire/spring-gemfire.xsd
|
||||
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
|
||||
">
|
||||
|
||||
<util:properties id="gemfireProperties">
|
||||
<prop key="name">SpringGemFireCachingIntegrationTest</prop>
|
||||
<prop key="mcast-port">0</prop>
|
||||
<prop key="log-level">warning</prop>
|
||||
</util:properties>
|
||||
|
||||
<gfe:cache properties-ref="gemfireProperties"/>
|
||||
|
||||
<gfe:region-template id="Template" initial-capacity="51" load-factor="0.75"/>
|
||||
|
||||
<cache:annotation-driven/>
|
||||
|
||||
<bean id="cacheManager" class="org.springframework.data.gemfire.support.GemfireCacheManager" p:cache-ref="gemfireCache"/>
|
||||
|
||||
<context:annotation-config/>
|
||||
|
||||
<util:map id="NamedNumbersMap" map-class="java.util.concurrent.ConcurrentHashMap"
|
||||
key-type="java.lang.String" value-type="java.lang.Integer">
|
||||
<entry key="zero" value="0"/>
|
||||
<entry key="one" value="1"/>
|
||||
<entry key="two" value="2"/>
|
||||
<entry key="three" value="3"/>
|
||||
<entry key="four" value="4"/>
|
||||
<entry key="five" value="5"/>
|
||||
<entry key="size" value="6"/>
|
||||
<entry key="seven" value="7"/>
|
||||
<entry key="eight" value="8"/>
|
||||
<entry key="nine" value="9"/>
|
||||
</util:map>
|
||||
|
||||
<bean id="namedNumbersRepo" class="org.springframework.data.gemfire.support.CachingWithGemFireIntegrationTest$NamedNumbersInMemoryRepository">
|
||||
<property name="namedNumbers" ref="NamedNumbersMap"/>
|
||||
</bean>
|
||||
|
||||
<bean id="namedNumbersService" class="org.springframework.data.gemfire.support.CachingWithGemFireIntegrationTest$NamedNumbersService">
|
||||
<property name="namedNumbersRepo" ref="namedNumbersRepo"/>
|
||||
</bean>
|
||||
|
||||
<beans profile="replica">
|
||||
<gfe:replicated-region id="NamedNumbersRegion" persistent="false" key-constraint="java.lang.String"
|
||||
value-constraint="java.lang.Integer" template="Template"/>
|
||||
</beans>
|
||||
|
||||
<beans profile="partition">
|
||||
<gfe:partitioned-region id="NamedNumbersRegion" persistent="false" key-constraint="java.lang.String"
|
||||
value-constraint="java.lang.Integer" template="Template"/>
|
||||
</beans>
|
||||
|
||||
<beans profile="local">
|
||||
<gfe:partitioned-region id="NamedNumbersRegion" persistent="false" key-constraint="java.lang.String"
|
||||
value-constraint="java.lang.Integer" template="Template"/>
|
||||
</beans>
|
||||
|
||||
</beans>
|
||||
Reference in New Issue
Block a user