diff --git a/build.gradle b/build.gradle
index 8f670ba5..20d0f2d6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -102,14 +102,16 @@ 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 "org.apache.openwebbeans.test:cditest-owb:$openwebbeansVersion"
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 477f6663..215f52be 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,19 +1,20 @@
antlrVersion=2.7.7
-aspectjVersion=1.8.5
+aspectjVersion=1.8.9
assertjVersion=3.5.2
cdiVersion=1.0
gemfireVersion=8.2.0
hamcrestVersion=1.3
-jacksonVersion=2.6.0
+jacksonVersion=2.6.7
junitVersion=4.12
log4jVersion=1.2.17
+lombokVersion=1.16.10
mockitoVersion=1.10.19
multiThreadedtcVersion=1.01
openwebbeansVersion=1.2.8
servletApiVersion=2.5
-slf4jVersion=1.7.12
+slf4jVersion=1.7.21
spring.range="[4.0.0, 5.0.0)"
-springVersion=4.2.6.RELEASE
-springDataBuildVersion=1.8.3.BUILD-SNAPSHOT
-springDataCommonsVersion=1.12.3.BUILD-SNAPSHOT
-version=1.8.3.BUILD-SNAPSHOT
+springVersion=4.2.8.RELEASE
+springDataBuildVersion=1.8.5.BUILD-SNAPSHOT
+springDataCommonsVersion=1.12.5.BUILD-SNAPSHOT
+version=1.8.5.BUILD-SNAPSHOT
diff --git a/pom.xml b/pom.xml
index ab63264c..550c57dd 100644
--- a/pom.xml
+++ b/pom.xml
@@ -3,18 +3,18 @@
4.0.0
- org.springframework.data
- spring-data-gemfire
- 1.8.5.BUILD-SNAPSHOT
-
- Spring Data Gemfire
-
org.springframework.data.build
spring-data-parent
1.8.5.BUILD-SNAPSHOT
+ org.springframework.data
+ spring-data-gemfire
+ 1.8.5.BUILD-SNAPSHOT
+
+ Spring Data Gemfire
+
SGF
2.7.7
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 @@
-
+