SGF-539 - Change GemfireCache.evict(key) to call Region.remove(key).

(cherry picked from commit a4abc83adbc5e56ac47f3228b297a2613bdc3565)
Signed-off-by: John Blum <jblum@pivotal.io>
This commit is contained in:
John Blum
2016-10-03 23:59:54 -07:00
parent f64765ef70
commit c40ef58741
7 changed files with 368 additions and 6 deletions

View File

@@ -123,14 +123,17 @@ dependencies {
testCompile("org.springframework:spring-test:$springVersion") {
exclude group: "commons-logging", module: "commons-logging"
}
testCompile "junit:junit:$junitVersion"
testCompile "org.apache.openwebbeans.test:cditest-owb:$openwebbeansVersion"
testCompile "org.assertj:assertj-core:$assertjVersion"
testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion"
testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion"
testCompile "org.projectlombok:lombok:$lombokVersion"
testCompile "org.mockito:mockito-core:$mockitoVersion"
testCompile "junit:junit:$junitVersion"
testCompile "edu.umd.cs.mtc:multithreadedtc:$multiThreadedtcVersion"
testCompile "javax.annotation:jsr250-api:1.0", optional
testRuntime "javax.el:el-api:$cdiVersion"
testRuntime "javax.servlet:servlet-api:$servletApiVersion"
testRuntime "log4j:log4j:$log4jVersion"

View File

@@ -8,6 +8,7 @@ hamcrestVersion=1.3
jacksonVersion=2.7.6
junitVersion=4.12
log4jVersion=1.2.17
lombokVersion=1.16.10
mockitoVersion=1.10.19
multiThreadedtcVersion=1.01
openwebbeansVersion=1.2.8

View File

@@ -101,7 +101,7 @@ public class GemfireCache implements Cache {
* @see com.gemstone.gemfire.cache.Region#destroy(Object)
*/
public void evict(Object key) {
getNativeCache().destroy(key);
getNativeCache().remove(key);
}
/**

View File

@@ -103,7 +103,7 @@ public class GemfireCacheManager extends AbstractCacheManager {
Collection<Cache> caches = new HashSet<Cache>(regions.size());
for (Region<?, ?> region : regions) {
caches.add(GemfireCache.wrap(region));
caches.add(newGemfireCache(region));
}
return caches;
@@ -136,6 +136,18 @@ public class GemfireCacheManager extends AbstractCacheManager {
return (collection != null && collection.iterator().hasNext());
}
/**
* Constructs a new instance of {@link GemfireCache} initialized with the given GemFire {@link Region}.
*
* @param region GemFire {@link Region} to wrap (adapt).
* @return an instance of {@link GemfireCache} initialized with the given GemFire {@link Region}.
* @see org.springframework.data.gemfire.support.GemfireCache
* @see com.gemstone.gemfire.cache.Region
*/
protected GemfireCache newGemfireCache(Region<?, ?> region) {
return GemfireCache.wrap(region);
}
/* (non-Javadoc) */
Region<?, ?> regionFor(GemFireCache gemfireCache, String cacheName) {
return assertGemFireRegionAvailable(assertGemFireCacheAvailable(gemfireCache).getRegion(cacheName), cacheName);
@@ -159,7 +171,7 @@ public class GemfireCacheManager extends AbstractCacheManager {
protected Cache getMissingCache(String name) {
Cache cache = super.getMissingCache(name);
return (cache != null ? cache : (isDynamic() ? GemfireCache.wrap(regionFor(this.gemfireCache, name)) : null));
return (cache != null ? cache : (isDynamic() ? newGemfireCache(regionFor(this.gemfireCache, name)) : null));
}
/**

View File

@@ -0,0 +1,344 @@
/*
* Copyright 2012 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.assertj.core.api.Assertions.assertThat;
import java.io.Serializable;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.Resource;
import com.gemstone.gemfire.cache.GemFireCache;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.annotation.Id;
import org.springframework.data.gemfire.CacheFactoryBean;
import org.springframework.data.gemfire.LocalRegionFactoryBean;
import org.springframework.data.gemfire.mapping.GemfireMappingContext;
import org.springframework.data.gemfire.mapping.Region;
import org.springframework.data.gemfire.repository.support.GemfireRepositoryFactoryBean;
import org.springframework.data.gemfire.test.support.IdentifierSequence;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Service;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
/**
* Integration tests testing the contractual behavior and combination of using Spring'a {@link CachePut} annotation
* followed by a {@link CacheEvict} annotation on an application {@link @Service} component.
*
* @author John Blum
* @see org.junit.Test
* @see org.springframework.cache.annotation.CacheEvict
* @see org.springframework.cache.annotation.CachePut
* @see org.springframework.cache.annotation.Caching
* @see org.springframework.cache.annotation.EnableCaching
* @see org.springframework.test.context.ContextConfiguration
* @see org.springframework.test.context.junit4.SpringJUnit4ClassRunner
* @see org.springframework.data.gemfire.support.GemfireCache#evict(Object)
* @see org.springframework.data.gemfire.support.GemfireCache#put(Object, Object)
* @see <a href="http://stackoverflow.com/questions/39830488/gemfire-entrynotfoundexception-for-cacheevict">Gemfire EntryNotFoundException on @CacheEvict</a>
* @see <a href="https://jira.spring.io/browse/SGF-539">Change GemfireCache.evict(key) to call Region.remove(key)</a>
* @since 1.9.0
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CompoundCachePutCacheEvictIntegrationTests.ApplicationTestConfiguration.class)
@SuppressWarnings("unused")
public class CompoundCachePutCacheEvictIntegrationTests {
private Person janeDoe;
private Person jonDoe;
@Autowired
private PeopleService peopleService;
@Resource(name = "People")
private com.gemstone.gemfire.cache.Region<Long, Person> peopleRegion;
protected void assertNoPeopleInDepartment(Department department) {
assertPeopleInDepartment(department);
}
protected void assertPeopleInDepartment(Department department, Person... people) {
List<Person> peopleInDepartment = peopleService.findByDepartment(department);
assertThat(peopleInDepartment).isNotNull();
assertThat(peopleInDepartment.size()).isEqualTo(people.length);
assertThat(peopleInDepartment).contains(people);
}
protected Person newPerson(String name, String mobile, Department department) {
return newPerson(IdentifierSequence.nextId(), name, mobile, department);
}
protected Person newPerson(Long id, String name, String mobile, Department department) {
Person person = Person.newPerson(department, mobile, name);
person.setId(id);
return person;
}
protected Person save(Person person) {
peopleRegion.put(person.getId(), person);
return person;
}
@Before
public void setup() {
janeDoe = save(newPerson("Jane Doe", "541-555-1234", Department.MARKETING));
jonDoe = save(newPerson("Jon Doe", "972-555-1248", Department.ENGINEERING));
assertThat(peopleRegion.containsValue(janeDoe)).isTrue();
assertThat(peopleRegion.containsValue(janeDoe)).isTrue();
}
@Test
public void janeDoeUpdateSuccessful() {
assertNoPeopleInDepartment(Department.DESIGN);
assertThat(peopleService.isCacheMiss()).isTrue();
janeDoe.setDepartment(Department.DESIGN);
peopleService.update(janeDoe);
assertPeopleInDepartment(Department.DESIGN, janeDoe);
assertThat(peopleService.isCacheMiss()).isTrue();
}
@Test
public void jonDoeUpdateSuccessful() {
jonDoe.setDepartment(Department.RESEARCH_DEVELOPMENT);
peopleService.update(jonDoe);
assertPeopleInDepartment(Department.RESEARCH_DEVELOPMENT, jonDoe);
assertThat(peopleService.isCacheMiss()).isTrue();
}
@Configuration
@EnableCaching
@Import(ApplicationTestConfiguration.class)
static class Sgf539WorkaroundConfiguration {
@Bean
GemfireCacheManager cacheManager(GemFireCache gemfireCache) {
GemfireCacheManager cacheManager = new GemfireCacheManager() {
@Override protected org.springframework.cache.Cache decorateCache(org.springframework.cache.Cache cache) {
return new GemfireCache((com.gemstone.gemfire.cache.Region<?, ?>) cache.getNativeCache()) {
@Override public void evict(Object key) {
getNativeCache().remove(key);
}
};
}
};
cacheManager.setCache(gemfireCache);
return cacheManager;
}
}
@Configuration
@EnableCaching
@Import(GemFireConfiguration.class)
static class ApplicationTestConfiguration {
@Bean
GemfireCacheManager cacheManager(GemFireCache gemfireCache) {
GemfireCacheManager cacheManager = new GemfireCacheManager();
cacheManager.setCache(gemfireCache);
return cacheManager;
}
@Bean
GemfireRepositoryFactoryBean<PersonRepository, Person, Long> personRepository() {
GemfireRepositoryFactoryBean<PersonRepository, Person, Long> personRepository =
new GemfireRepositoryFactoryBean<PersonRepository, Person, Long>();
personRepository.setGemfireMappingContext(new GemfireMappingContext());
personRepository.setRepositoryInterface(PersonRepository.class);
return personRepository;
}
@Bean
PeopleService peopleService(PersonRepository personRepository) {
return new PeopleService(personRepository);
}
}
@Configuration
static class GemFireConfiguration {
static final String DEFAULT_GEMFIRE_LOG_LEVEL = "warning";
Properties gemfireProperties() {
Properties gemfireProperties = new Properties();
gemfireProperties.setProperty("name", applicationName());
gemfireProperties.setProperty("mcast-port", "0");
gemfireProperties.setProperty("locators", "");
gemfireProperties.setProperty("log-level", logLevel());
return gemfireProperties;
}
String applicationName() {
return CompoundCachePutCacheEvictIntegrationTests.class.getName();
}
String logLevel() {
return System.getProperty("gemfire.log.level", DEFAULT_GEMFIRE_LOG_LEVEL);
}
@Bean CacheFactoryBean gemfireCache() {
CacheFactoryBean gemfireCache = new CacheFactoryBean();
gemfireCache.setClose(true);
gemfireCache.setProperties(gemfireProperties());
return gemfireCache;
}
@Bean(name = "People") LocalRegionFactoryBean<Long, Person> peopleRegion(GemFireCache gemfireCache) {
LocalRegionFactoryBean<Long, Person> peopleRegion = new LocalRegionFactoryBean<Long, Person>();
peopleRegion.setCache(gemfireCache);
peopleRegion.setClose(false);
peopleRegion.setPersistent(false);
return peopleRegion;
}
@Bean(name = "DepartmentPeople")
LocalRegionFactoryBean<Long, Person> departmentPeopleRegion(GemFireCache gemfireCache) {
LocalRegionFactoryBean<Long, Person> departmentPeopleRegion = new LocalRegionFactoryBean<Long, Person>();
departmentPeopleRegion.setCache(gemfireCache);
departmentPeopleRegion.setClose(false);
departmentPeopleRegion.setPersistent(false);
return departmentPeopleRegion;
}
@Bean(name = "MobilePeople")
LocalRegionFactoryBean<Long, Person> mobilePeopleRegion(GemFireCache gemfireCache) {
LocalRegionFactoryBean<Long, Person> mobilePeopleRegion = new LocalRegionFactoryBean<Long, Person>();
mobilePeopleRegion.setCache(gemfireCache);
mobilePeopleRegion.setClose(false);
mobilePeopleRegion.setPersistent(false);
return mobilePeopleRegion;
}
}
public enum Department {
ACCOUNTING,
DESIGN,
ENGINEERING,
LEGAL,
MANAGEMENT,
MARKETING,
RESEARCH_DEVELOPMENT,
SALES
}
@Data
@Region("People")
@RequiredArgsConstructor(staticName = "newPerson")
public static class Person implements Serializable {
@Id
private Long id;
@NonNull private Department department;
@NonNull private String mobile;
@NonNull private String name;
}
@Service
public static class PeopleService extends CacheableService {
private final PersonRepository personRepository;
public PeopleService(PersonRepository personRepository) {
this.personRepository = personRepository;
}
@Cacheable("DepartmentPeople")
public List<Person> findByDepartment(Department department) {
setCacheMiss();
return personRepository.findByDepartment(department);
}
@Cacheable("MobilePeople")
public Person findByMobile(String mobile) {
setCacheMiss();
return personRepository.findByMobile(mobile);
}
@Caching(
evict = @CacheEvict(value = "DepartmentPeople", key = "#p0.department"),
put = @CachePut(value = "MobilePeople", key="#p0.mobile")
)
public Person update(Person person) {
return personRepository.save(person);
}
}
protected static abstract class CacheableService {
private final AtomicBoolean cacheMiss = new AtomicBoolean(false);
public boolean isCacheMiss() {
return cacheMiss.compareAndSet(true, false);
}
public boolean isNotCacheMiss() {
return !isCacheMiss();
}
protected void setCacheMiss() {
this.cacheMiss.set(true);
}
}
public interface PersonRepository extends CrudRepository<Person, Long> {
List<Person> findByDepartment(Department department);
Person findByMobile(String mobile);
}
}

View File

@@ -97,9 +97,10 @@ public class GemfireCacheUnitTests {
}
@Test
public void evictCallsRegionDestoryWithKey() {
public void evictCallsRegionRemoveWithKey() {
GemfireCache.wrap(mockRegion).evict("key");
verify(mockRegion, times(1)).destroy(eq("key"));
verify(mockRegion, never()).destroy(anyObject());
verify(mockRegion, times(1)).remove(eq("key"));
}
@Test

View File

@@ -15,6 +15,7 @@
<context:component-scan base-package="org.springframework.data.gemfire">
<context:include-filter type="assignable" expression="org.springframework.data.gemfire.AutoRegionLookupDao"/>
<context:exclude-filter type="annotation" expression="org.springframework.context.annotation.Configuration"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
<context:exclude-filter type="assignable" expression="org.springframework.data.gemfire.AutoRegionLookupWithAutowiringIntegrationTests$TestComponent"/>
<context:exclude-filter type="regex" expression="org.springframework.data.gemfire.*.sample.*"/>
</context:component-scan>