From 402aa867eb28a4d2202dc17337fcc480a0dc8fe5 Mon Sep 17 00:00:00 2001 From: John Blum Date: Wed, 19 Jul 2017 20:51:19 -0700 Subject: [PATCH] DATAGEODE-23 - Add Annotation to configure and enable Spring's Transaction Management with Local, Geode Cache Transactions. --- .../EnableGemfireCacheTransactions.java | 52 ++++++ ...GemfireCacheTransactionsConfiguration.java | 56 +++++++ ...ireTransactionManagerIntegrationTests.java | 55 +++---- ...fireCacheTransactionsIntegrationTests.java | 149 ++++++++++++++++++ 4 files changed, 278 insertions(+), 34 deletions(-) create mode 100644 src/main/java/org/springframework/data/gemfire/transaction/config/EnableGemfireCacheTransactions.java create mode 100644 src/main/java/org/springframework/data/gemfire/transaction/config/GemfireCacheTransactionsConfiguration.java create mode 100644 src/test/java/org/springframework/data/gemfire/transaction/config/EnableGemfireCacheTransactionsIntegrationTests.java diff --git a/src/main/java/org/springframework/data/gemfire/transaction/config/EnableGemfireCacheTransactions.java b/src/main/java/org/springframework/data/gemfire/transaction/config/EnableGemfireCacheTransactions.java new file mode 100644 index 00000000..c0342d80 --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/transaction/config/EnableGemfireCacheTransactions.java @@ -0,0 +1,52 @@ +/* + * Copyright 2017 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.transaction.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.context.annotation.Import; + +/** + * The {@link EnableGemfireCacheTransactions} annotation enables Pivotal GemFire or Apache Geode Cache Transactions + * in Spring's Transaction Management infrastructure. + * + * @author John Blum + * @see java.lang.annotation.Documented + * @see java.lang.annotation.Inherited + * @see java.lang.annotation.Retention + * @see java.lang.annotation.Target + * @see org.springframework.context.annotation.Import + * @see Spring Transaction Management + * @see Spring Data GemFire Transaction Management + * @see GemFire Cache Transactions + * @see Geode Cache Transactions + * @since 2.0.0 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Inherited +@Documented +@Import(GemfireCacheTransactionsConfiguration.class) +@SuppressWarnings("unused") +public @interface EnableGemfireCacheTransactions { + +} diff --git a/src/main/java/org/springframework/data/gemfire/transaction/config/GemfireCacheTransactionsConfiguration.java b/src/main/java/org/springframework/data/gemfire/transaction/config/GemfireCacheTransactionsConfiguration.java new file mode 100644 index 00000000..f808771a --- /dev/null +++ b/src/main/java/org/springframework/data/gemfire/transaction/config/GemfireCacheTransactionsConfiguration.java @@ -0,0 +1,56 @@ +/* + * Copyright 2017 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.transaction.config; + +import org.apache.geode.cache.GemFireCache; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.gemfire.transaction.GemfireTransactionManager; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +/** + * The {@link GemfireCacheTransactionsConfiguration} class is a Spring {@link Configuration @Configuration} class + * used to enable Spring's Transaction Management infrastructure along with SDG's {@link GemfireTransactionManager} + * to manage local, cache transactions for either Pivotal GemFire or Apache Geode. + * + * @author John Blum + * @see org.apache.geode.cache.GemFireCache + * @see org.springframework.context.annotation.Bean + * @see org.springframework.context.annotation.Configuration + * @see org.springframework.data.gemfire.transaction.GemfireTransactionManager + * @see org.springframework.transaction.annotation.EnableTransactionManagement + * @since 2.0.0 + */ +@Configuration +@EnableTransactionManagement +@SuppressWarnings("unused") +public class GemfireCacheTransactionsConfiguration { + + /** + * Declares and registers SDG's {@link GemfireTransactionManager} as the {@literal transactionManager} + * in Spring's Transaction Management infrastructure to manage local, GemFire/Geode cache transactions. + * + * @param gemfireCache reference to the {@link GemFireCache}. + * @return a new instance of {@link GemfireTransactionManager} initialized with the given {@link GemFireCache}. + * @see org.springframework.data.gemfire.transaction.GemfireTransactionManager + * @see org.apache.geode.cache.GemFireCache + */ + @Bean + public GemfireTransactionManager transactionManager(GemFireCache gemfireCache) { + return new GemfireTransactionManager(gemfireCache); + } +} diff --git a/src/test/java/org/springframework/data/gemfire/transaction/GemfireTransactionManagerIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/transaction/GemfireTransactionManagerIntegrationTests.java index 5b8a8d91..9fa05255 100644 --- a/src/test/java/org/springframework/data/gemfire/transaction/GemfireTransactionManagerIntegrationTests.java +++ b/src/test/java/org/springframework/data/gemfire/transaction/GemfireTransactionManagerIntegrationTests.java @@ -27,15 +27,13 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Import; import org.springframework.data.gemfire.LocalRegionFactoryBean; import org.springframework.data.gemfire.config.annotation.PeerCacheApplication; +import org.springframework.data.gemfire.transaction.config.EnableGemfireCacheTransactions; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; -import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; @@ -54,11 +52,10 @@ import org.springframework.util.Assert; * @since 1.9.1 */ @RunWith(SpringRunner.class) -@ContextConfiguration(classes = - GemfireTransactionManagerIntegrationTests.GemfireTransactionManagerIntegrationTestsConfiguration.class) +@ContextConfiguration public class GemfireTransactionManagerIntegrationTests { - static final String GEMFIRE_LOG_LEVEL = "warning"; + private static final String GEMFIRE_LOG_LEVEL = "warning"; @Resource(name = "Example") @SuppressWarnings("unused") @@ -88,39 +85,15 @@ public class GemfireTransactionManagerIntegrationTests { } } - @Configuration - @EnableTransactionManagement - @Import(GemFireConfiguration.class) - @SuppressWarnings("unused") - static class GemfireTransactionManagerIntegrationTestsConfiguration { - - @Bean - GemfireTransactionManager transactionManager(GemFireCache gemfireCache) { - return new GemfireTransactionManager(gemfireCache); - } - - @Bean - SuspendAndResumeCacheTransactionsRepository suspendAndResumeCacheTransactionsRepository( - GemFireCache gemFireCache) { - - return new SuspendAndResumeCacheTransactionsRepository(gemFireCache.getRegion("Example")); - } - - @Bean - SuspendAndResumeCacheTransactionsService suspendAndResumeCacheTransactionsService( - SuspendAndResumeCacheTransactionsRepository repository) { - - return new SuspendAndResumeCacheTransactionsService(repository); - } - } - @SuppressWarnings("unused") + @EnableGemfireCacheTransactions @PeerCacheApplication(name = "GemfireTransactionManagerIntegrationTests", logLevel = GEMFIRE_LOG_LEVEL) - static class GemFireConfiguration { + static class TestConfiguration { @Bean(name = "Example") LocalRegionFactoryBean exampleRegion(GemFireCache gemfireCache) { - LocalRegionFactoryBean example = new LocalRegionFactoryBean(); + + LocalRegionFactoryBean example = new LocalRegionFactoryBean<>(); example.setCache(gemfireCache); example.setClose(false); @@ -128,6 +101,20 @@ public class GemfireTransactionManagerIntegrationTests { return example; } + + @Bean + SuspendAndResumeCacheTransactionsRepository suspendAndResumeCacheTransactionsRepository( + GemFireCache gemFireCache) { + + return new SuspendAndResumeCacheTransactionsRepository(gemFireCache.getRegion("Example")); + } + + @Bean + SuspendAndResumeCacheTransactionsService suspendAndResumeCacheTransactionsService( + SuspendAndResumeCacheTransactionsRepository repository) { + + return new SuspendAndResumeCacheTransactionsService(repository); + } } @Service diff --git a/src/test/java/org/springframework/data/gemfire/transaction/config/EnableGemfireCacheTransactionsIntegrationTests.java b/src/test/java/org/springframework/data/gemfire/transaction/config/EnableGemfireCacheTransactionsIntegrationTests.java new file mode 100644 index 00000000..95a00723 --- /dev/null +++ b/src/test/java/org/springframework/data/gemfire/transaction/config/EnableGemfireCacheTransactionsIntegrationTests.java @@ -0,0 +1,149 @@ +/* + * Copyright 2017 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.transaction.config; + +import static org.assertj.core.api.Java6Assertions.assertThat; + +import javax.annotation.Resource; + +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.Region; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.LocalRegionFactoryBean; +import org.springframework.data.gemfire.config.annotation.PeerCacheApplication; +import org.springframework.data.gemfire.transaction.GemfireTransactionManager; +import org.springframework.stereotype.Service; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.transaction.annotation.Transactional; + +/** + * Integration tests for {@link EnableGemfireCacheTransactions} and {@link GemfireCacheTransactionsConfiguration}. + * + * @author John Blum + * @see org.junit.Test + * @see org.junit.runner.RunWith + * @see org.apache.geode.cache.GemFireCache + * @see org.apache.geode.cache.Region + * @see org.springframework.context.annotation.Bean + * @see org.springframework.data.gemfire.config.annotation.PeerCacheApplication + * @see org.springframework.data.gemfire.transaction.GemfireTransactionManager + * @see org.springframework.data.gemfire.transaction.config.EnableGemfireCacheTransactions + * @see org.springframework.data.gemfire.transaction.config.GemfireCacheTransactionsConfiguration + * @see org.springframework.test.context.ContextConfiguration + * @see org.springframework.test.context.junit4.SpringRunner + * @see org.springframework.transaction.annotation.Transactional + * @since 2.0.0 + */ +@RunWith(SpringRunner.class) +@ContextConfiguration +@SuppressWarnings("unused") +public class EnableGemfireCacheTransactionsIntegrationTests { + + @Resource(name = "Example") + private Region example; + + @Autowired + private GemFireCache gemfireCache; + + @Autowired + private GemfireTransactionManager transactionManager; + + @Autowired + private TestTransactionalService transactionalService; + + @Test + public void transactionManagerIsConfigured() { + assertThat(this.gemfireCache).isNotNull(); + assertThat(this.transactionManager).isNotNull(); + assertThat(this.transactionManager.getCache()).isSameAs(this.gemfireCache); + } + + @Test + public void doInTransactionCommits() { + + assertThat(example).isNotNull(); + assertThat(example).isEmpty(); + assertThat(transactionalService.doInTransactionCommits(1, "pass")).isTrue(); + assertThat(example).hasSize(1); + assertThat(example).containsKey(1); + assertThat(example.get(1)).isEqualTo("pass"); + } + + @Test(expected = RuntimeException.class) + public void doInTransactionRollsback() { + try { + assertThat(example).doesNotContainKey(2); + transactionalService.doInTransactionRollsBack(2, "fail"); + } + catch (RuntimeException expected) { + assertThat(expected).hasMessage("test"); + assertThat(expected).hasNoCause(); + + throw expected; + } + finally { + assertThat(example).doesNotContainKey(2); + } + } + + @SuppressWarnings("unused") + @EnableGemfireCacheTransactions + @PeerCacheApplication(name = "EnableGemfireCacheTransactionsIntegrationTests", logLevel = "warning") + static class TestConfiguration { + + @Bean("Example") + public LocalRegionFactoryBean exampleRegion(GemFireCache gemFireCache) { + + LocalRegionFactoryBean example = new LocalRegionFactoryBean<>(); + + example.setCache(gemFireCache); + example.setClose(false); + example.setPersistent(false); + + return example; + } + + @Bean + TestTransactionalService transactionalService() { + return new TestTransactionalService(); + } + } + + @Service + @SuppressWarnings("all") + static class TestTransactionalService { + + @Resource(name = "Example") + private Region example; + + @Transactional + public boolean doInTransactionCommits(Object key, Object value) { + example.put(key, value); + return true; + } + + @Transactional + public boolean doInTransactionRollsBack(Object key, Object value) { + example.put(2, "fail"); + throw new RuntimeException("test"); + } + } +}