From c40ef58741da482bc4367eb6d05d307c543136c2 Mon Sep 17 00:00:00 2001 From: John Blum Date: Mon, 3 Oct 2016 23:59:54 -0700 Subject: [PATCH] SGF-539 - Change GemfireCache.evict(key) to call Region.remove(key). (cherry picked from commit a4abc83adbc5e56ac47f3228b297a2613bdc3565) Signed-off-by: John Blum --- build.gradle | 5 +- gradle.properties | 1 + .../data/gemfire/support/GemfireCache.java | 2 +- .../gemfire/support/GemfireCacheManager.java | 16 +- ...undCachePutCacheEvictIntegrationTests.java | 344 ++++++++++++++++++ .../support/GemfireCacheUnitTests.java | 5 +- ...ponentScanningIntegrationTests-context.xml | 1 + 7 files changed, 368 insertions(+), 6 deletions(-) create mode 100644 src/test/java/org/springframework/data/gemfire/support/CompoundCachePutCacheEvictIntegrationTests.java diff --git a/build.gradle b/build.gradle index 668284c5..0890a4e3 100644 --- a/build.gradle +++ b/build.gradle @@ -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" diff --git a/gradle.properties b/gradle.properties index df131faf..b2797a89 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/src/main/java/org/springframework/data/gemfire/support/GemfireCache.java b/src/main/java/org/springframework/data/gemfire/support/GemfireCache.java index 5287387a..c4a3a29a 100644 --- a/src/main/java/org/springframework/data/gemfire/support/GemfireCache.java +++ b/src/main/java/org/springframework/data/gemfire/support/GemfireCache.java @@ -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); } /** diff --git a/src/main/java/org/springframework/data/gemfire/support/GemfireCacheManager.java b/src/main/java/org/springframework/data/gemfire/support/GemfireCacheManager.java index cf854628..42b8e3af 100644 --- a/src/main/java/org/springframework/data/gemfire/support/GemfireCacheManager.java +++ b/src/main/java/org/springframework/data/gemfire/support/GemfireCacheManager.java @@ -103,7 +103,7 @@ public class GemfireCacheManager extends AbstractCacheManager { Collection caches = new HashSet(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)); } /** diff --git a/src/test/java/org/springframework/data/gemfire/support/CompoundCachePutCacheEvictIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/support/CompoundCachePutCacheEvictIntegrationTests.java new file mode 100644 index 00000000..af5784dd --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/support/CompoundCachePutCacheEvictIntegrationTests.java @@ -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 Gemfire EntryNotFoundException on @CacheEvict + * @see Change GemfireCache.evict(key) to call Region.remove(key) + * @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 peopleRegion; + + protected void assertNoPeopleInDepartment(Department department) { + assertPeopleInDepartment(department); + } + + protected void assertPeopleInDepartment(Department department, Person... people) { + List 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() { + GemfireRepositoryFactoryBean personRepository = + new GemfireRepositoryFactoryBean(); + + 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 peopleRegion(GemFireCache gemfireCache) { + LocalRegionFactoryBean peopleRegion = new LocalRegionFactoryBean(); + + peopleRegion.setCache(gemfireCache); + peopleRegion.setClose(false); + peopleRegion.setPersistent(false); + + return peopleRegion; + } + + @Bean(name = "DepartmentPeople") + LocalRegionFactoryBean departmentPeopleRegion(GemFireCache gemfireCache) { + LocalRegionFactoryBean departmentPeopleRegion = new LocalRegionFactoryBean(); + + departmentPeopleRegion.setCache(gemfireCache); + departmentPeopleRegion.setClose(false); + departmentPeopleRegion.setPersistent(false); + + return departmentPeopleRegion; + } + + @Bean(name = "MobilePeople") + LocalRegionFactoryBean mobilePeopleRegion(GemFireCache gemfireCache) { + LocalRegionFactoryBean mobilePeopleRegion = new LocalRegionFactoryBean(); + + 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 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 { + + List findByDepartment(Department department); + + Person findByMobile(String mobile); + + } +} diff --git a/src/test/java/org/springframework/data/gemfire/support/GemfireCacheUnitTests.java b/src/test/java/org/springframework/data/gemfire/support/GemfireCacheUnitTests.java index 7060aed2..f3fff7cf 100644 --- a/src/test/java/org/springframework/data/gemfire/support/GemfireCacheUnitTests.java +++ b/src/test/java/org/springframework/data/gemfire/support/GemfireCacheUnitTests.java @@ -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 diff --git a/src/test/resources/org/springframework/data/gemfire/AutoRegionLookupWithComponentScanningIntegrationTests-context.xml b/src/test/resources/org/springframework/data/gemfire/AutoRegionLookupWithComponentScanningIntegrationTests-context.xml index 9b5daba9..9abb2aff 100644 --- a/src/test/resources/org/springframework/data/gemfire/AutoRegionLookupWithComponentScanningIntegrationTests-context.xml +++ b/src/test/resources/org/springframework/data/gemfire/AutoRegionLookupWithComponentScanningIntegrationTests-context.xml @@ -15,6 +15,7 @@ +