From 0ea633e91c5154dce5db5e94ad59be716f14d65b 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 da7547ab1a1be9798f96dcbc14ed78e842a42b8f) Signed-off-by: John Blum --- build.gradle | 5 +- gradle.properties | 13 +- .../data/gemfire/support/GemfireCache.java | 21 +- .../gemfire/support/GemfireCacheManager.java | 37 +- ...undCachePutCacheEvictIntegrationTests.java | 344 ++++++++++++++++++ ...ponentScanningIntegrationTests-context.xml | 1 + ...stenceExceptionTranslationTest-context.xml | 4 +- 7 files changed, 395 insertions(+), 30 deletions(-) create mode 100644 src/test/java/org/springframework/data/gemfire/support/CompoundCachePutCacheEvictIntegrationTests.java diff --git a/build.gradle b/build.gradle index 1f6e4392..924acbc8 100644 --- a/build.gradle +++ b/build.gradle @@ -108,16 +108,19 @@ dependencies { exclude group: "commons-logging", module: "commons-logging" } testCompile "org.assertj:assertj-core:$assertjVersion" - testCompile "junit:junit:$junitVersion" 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 "log4j:log4j:$log4jVersion" testRuntime "org.apache.derby:derbyLocale_zh_TW:10.9.1.0" testRuntime "org.slf4j:slf4j-log4j12:$slf4jVersion" + testRuntime "org.springframework.shell:spring-shell:1.0.0.RELEASE" sharedResources "org.springframework.data.build:spring-data-build-resources:$springDataBuildVersion@zip" } diff --git a/gradle.properties b/gradle.properties index 1b5c2041..3174ed64 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,17 +1,18 @@ antlrVersion=2.7.7 -aspectjVersion=1.8.5 +aspectjVersion=1.8.9 assertjVersion=3.5.2 gemfireVersion=8.1.0 googleCodeFindbugsVersion=2.0.2 hamcrestVersion=1.3 -jacksonVersion=2.6.4 +jacksonVersion=2.6.7 junitVersion=4.12 log4jVersion=1.2.17 +lombokVersion=1.16.10 mockitoVersion=1.10.19 multiThreadedtcVersion=1.01 -slf4jVersion=1.7.12 +slf4jVersion=1.7.21 spring.range="[4.0.0, 5.0.0)" springVersion=4.1.9.RELEASE -springDataBuildVersion=1.7.6.BUILD-SNAPSHOT -springDataCommonsVersion=1.11.6.BUILD-SNAPSHOT -version=1.7.5.BUILD-SNAPSHOT +springDataBuildVersion=1.7.7.BUILD-SNAPSHOT +springDataCommonsVersion=1.11.7.BUILD-SNAPSHOT +version=1.7.7.BUILD-SNAPSHOT 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 d8bb1478..980bcd96 100644 --- a/src/main/java/org/springframework/data/gemfire/support/GemfireCache.java +++ b/src/main/java/org/springframework/data/gemfire/support/GemfireCache.java @@ -18,13 +18,13 @@ package org.springframework.data.gemfire.support; import java.util.concurrent.Callable; +import com.gemstone.gemfire.cache.GemFireCache; +import com.gemstone.gemfire.cache.Region; + import org.springframework.cache.Cache; import org.springframework.cache.support.SimpleValueWrapper; import org.springframework.util.ObjectUtils; -import com.gemstone.gemfire.cache.GemFireCache; -import com.gemstone.gemfire.cache.Region; - /** * Spring Framework {@link Cache} implementation backed by a GemFire {@link Region}. * @@ -43,7 +43,7 @@ public class GemfireCache implements Cache { /** * Creates a {@link GemFireCache} instance. - * + * * @param region backing GemFire region */ public GemfireCache(final Region region) { @@ -62,8 +62,14 @@ public class GemfireCache implements Cache { region.clear(); } - public void evict(final Object key) { - region.destroy(key); + /** + * Evicts (destroys) the entry (key/value) mapped to the given key from this Spring {@link Cache}. + * + * @param key key used to identify the cache entry to evict. + * @see com.gemstone.gemfire.cache.Region#destroy(Object) + */ + public void evict(Object key) { + getNativeCache().remove(key); } public ValueWrapper get(final Object key) { @@ -121,7 +127,7 @@ public class GemfireCache implements Cache { /** * Implementation to satisfy extension of the {@link Cache} interface in Spring 4.1. Don't add the {@link Override} * annotation as this will break the compilation on 4.0. - * + * * @see org.springframework.cache.Cache#putIfAbsent(java.lang.Object, java.lang.Object) */ @SuppressWarnings("unchecked") @@ -130,5 +136,4 @@ public class GemfireCache implements Cache { return (existingValue == null ? null : new SimpleValueWrapper(existingValue)); } - } 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 c58e6524..383439c4 100644 --- a/src/main/java/org/springframework/data/gemfire/support/GemfireCacheManager.java +++ b/src/main/java/org/springframework/data/gemfire/support/GemfireCacheManager.java @@ -20,30 +20,31 @@ import java.util.Collection; import java.util.LinkedHashSet; import java.util.Set; +import com.gemstone.gemfire.cache.GemFireCache; +import com.gemstone.gemfire.cache.Region; + import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.support.AbstractCacheManager; import org.springframework.util.Assert; -import com.gemstone.gemfire.cache.Region; - /** * Spring Framework {@link CacheManager} backed by a Gemfire {@link com.gemstone.gemfire.cache.Cache}. Automatically * discovers the created caches (or {@link Region}s in Gemfire terminology). - * + * * @author Costin Leau * @author David Turanski * @author John Blum * @see org.springframework.cache.Cache * @see org.springframework.cache.CacheManager * @see org.springframework.cache.support.AbstractCacheManager - * @see com.gemstone.gemfire.cache.Cache + * @see com.gemstone.gemfire.cache.GemFireCache * @see com.gemstone.gemfire.cache.Region */ @SuppressWarnings("unused") public class GemfireCacheManager extends AbstractCacheManager { - private com.gemstone.gemfire.cache.Cache gemfireCache; + private com.gemstone.gemfire.cache.GemFireCache gemfireCache; private Set> regions; @@ -61,12 +62,12 @@ public class GemfireCacheManager extends AbstractCacheManager { Assert.state(!gemfireCache.isClosed(), "The GemFire Cache is closed; an open instance is required."); regions = gemfireCache.rootRegions(); - } + } Collection caches = new LinkedHashSet(regions.size()); for (Region region: this.regions) { - caches.add(new GemfireCache(region)); + caches.add(newGemfireCache(region)); } return caches; @@ -80,6 +81,7 @@ public class GemfireCacheManager extends AbstractCacheManager { * @see org.springframework.cache.Cache */ @Override + @SuppressWarnings("deprecation") public Cache getCache(String name) { Cache cache = super.getCache(name); @@ -88,7 +90,7 @@ public class GemfireCacheManager extends AbstractCacheManager { Region region = gemfireCache.getRegion(name); if (region != null) { - cache = new GemfireCache(region); + cache = newGemfireCache(region); addCache(cache); } } @@ -96,16 +98,28 @@ public class GemfireCacheManager extends AbstractCacheManager { return cache; } + /** + * 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 new GemfireCache(region); + } + /** * Sets the GemFire Cache backing this {@link CacheManager}. - * + * * @param gemfireCache the GemFire Peer Cache instance. * @see com.gemstone.gemfire.cache.Cache */ - public void setCache(com.gemstone.gemfire.cache.Cache gemfireCache) { + public void setCache(GemFireCache gemfireCache) { this.gemfireCache = gemfireCache; } - + /** * Sets the Regions to use (alternative to injecting the GemFire Cache). * @@ -115,5 +129,4 @@ public class GemfireCacheManager extends AbstractCacheManager { public void setRegions(Set> regions) { this.regions = regions; } - } 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/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 @@ + diff --git a/src/test/resources/org/springframework/data/gemfire/support/GemfirePersistenceExceptionTranslationTest-context.xml b/src/test/resources/org/springframework/data/gemfire/support/GemfirePersistenceExceptionTranslationTest-context.xml index 55bed3cd..58f45d4d 100644 --- a/src/test/resources/org/springframework/data/gemfire/support/GemfirePersistenceExceptionTranslationTest-context.xml +++ b/src/test/resources/org/springframework/data/gemfire/support/GemfirePersistenceExceptionTranslationTest-context.xml @@ -1,12 +1,10 @@ @@ -21,6 +19,6 @@ - +