SGF-897 - Adapt TransactionListener to enable auto transaction event publishing in the Spring ApplicationContext.
This commit is contained in:
@@ -885,7 +885,7 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @return the copyOnRead
|
||||
*/
|
||||
public Boolean getCopyOnRead() {
|
||||
return copyOnRead;
|
||||
return this.copyOnRead;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -901,7 +901,7 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @return the criticalHeapPercentage
|
||||
*/
|
||||
public Float getCriticalHeapPercentage() {
|
||||
return criticalHeapPercentage;
|
||||
return this.criticalHeapPercentage;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -956,7 +956,7 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @return the evictionHeapPercentage
|
||||
*/
|
||||
public Float getEvictionHeapPercentage() {
|
||||
return evictionHeapPercentage;
|
||||
return this.evictionHeapPercentage;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -989,7 +989,7 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @return the gatewayConflictResolver
|
||||
*/
|
||||
public GatewayConflictResolver getGatewayConflictResolver() {
|
||||
return gatewayConflictResolver;
|
||||
return this.gatewayConflictResolver;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1003,7 +1003,7 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @return the list of configured JndiDataSources.
|
||||
*/
|
||||
public List<JndiDataSource> getJndiDataSources() {
|
||||
return jndiDataSources;
|
||||
return this.jndiDataSources;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1019,7 +1019,7 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @return the lockLease
|
||||
*/
|
||||
public Integer getLockLease() {
|
||||
return lockLease;
|
||||
return this.lockLease;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1035,7 +1035,7 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @return the lockTimeout
|
||||
*/
|
||||
public Integer getLockTimeout() {
|
||||
return lockTimeout;
|
||||
return this.lockTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1054,7 +1054,7 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @return the messageSyncInterval
|
||||
*/
|
||||
public Integer getMessageSyncInterval() {
|
||||
return messageSyncInterval;
|
||||
return this.messageSyncInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1094,7 +1094,7 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @return the pdxDiskStoreName
|
||||
*/
|
||||
public String getPdxDiskStoreName() {
|
||||
return pdxDiskStoreName;
|
||||
return this.pdxDiskStoreName;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1111,7 +1111,7 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @return the pdxIgnoreUnreadFields
|
||||
*/
|
||||
public Boolean getPdxIgnoreUnreadFields() {
|
||||
return pdxIgnoreUnreadFields;
|
||||
return this.pdxIgnoreUnreadFields;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1127,7 +1127,7 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @return the pdxPersistent
|
||||
*/
|
||||
public Boolean getPdxPersistent() {
|
||||
return pdxPersistent;
|
||||
return this.pdxPersistent;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1144,7 +1144,7 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @return the pdxReadSerialized
|
||||
*/
|
||||
public Boolean getPdxReadSerialized() {
|
||||
return pdxReadSerialized;
|
||||
return this.pdxReadSerialized;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1162,7 +1162,7 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @return the pdxSerializer
|
||||
*/
|
||||
public PdxSerializer getPdxSerializer() {
|
||||
return pdxSerializer;
|
||||
return this.pdxSerializer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1203,7 +1203,7 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @return the searchTimeout
|
||||
*/
|
||||
public Integer getSearchTimeout() {
|
||||
return searchTimeout;
|
||||
return this.searchTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1223,7 +1223,7 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @see org.apache.geode.security.SecurityManager
|
||||
*/
|
||||
public SecurityManager getSecurityManager() {
|
||||
return securityManager;
|
||||
return this.securityManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1241,14 +1241,14 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
* @return the transactionListeners
|
||||
*/
|
||||
public List<TransactionListener> getTransactionListeners() {
|
||||
return transactionListeners;
|
||||
return this.transactionListeners;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the TransactionWriter used to configure the Cache for handling transaction events, such as to veto
|
||||
* Sets the {@link TransactionWriter} used to configure the cache for handling transaction events, such as to veto
|
||||
* the transaction or update an external DB before the commit.
|
||||
*
|
||||
* @param transactionWriter the Pivotal GemFire TransactionWriter callback receiving transaction events.
|
||||
* @param transactionWriter configured {@link TransactionWriter} callback receiving transaction events.
|
||||
* @see org.apache.geode.cache.TransactionWriter
|
||||
*/
|
||||
public void setTransactionWriter(TransactionWriter transactionWriter) {
|
||||
@@ -1256,10 +1256,13 @@ public class CacheFactoryBean extends AbstractFactoryBeanSupport<GemFireCache>
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the transactionWriter
|
||||
* Return the configured {@link TransactionWriter} used to process and handle transaction events.
|
||||
*
|
||||
* @return the configured {@link TransactionWriter}.
|
||||
* @see org.apache.geode.cache.TransactionWriter
|
||||
*/
|
||||
public TransactionWriter getTransactionWriter() {
|
||||
return transactionWriter;
|
||||
return this.transactionWriter;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -187,7 +187,8 @@ public class GemfireTransactionManager extends AbstractPlatformTransactionManage
|
||||
}
|
||||
catch (Exception cause) {
|
||||
throw new NoTransactionException(
|
||||
"No transaction is associated with the current thread; are multiple transaction managers present?", cause);
|
||||
"No transaction is associated with the current thread. Are multiple transaction managers present?",
|
||||
cause);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,7 +275,8 @@ public class GemfireTransactionManager extends AbstractPlatformTransactionManage
|
||||
|
||||
/**
|
||||
* Sets a reference to the {@link GemFireCache} for which this transaction manager
|
||||
* will manage local cache transactions.
|
||||
* manages local cache transactions.
|
||||
*
|
||||
* @param cache reference to the {@link GemFireCache}.
|
||||
* @see org.apache.geode.cache.GemFireCache
|
||||
*/
|
||||
@@ -284,7 +286,7 @@ public class GemfireTransactionManager extends AbstractPlatformTransactionManage
|
||||
|
||||
/**
|
||||
* Returns a reference to the {@link GemFireCache} for which this transaction manager
|
||||
* will manage local cache transactions.
|
||||
* manages local cache transactions.
|
||||
*
|
||||
* @return a reference to the {@link GemFireCache}.
|
||||
* @see org.apache.geode.cache.GemFireCache
|
||||
@@ -293,6 +295,14 @@ public class GemfireTransactionManager extends AbstractPlatformTransactionManage
|
||||
return this.cache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a reference to the {@link CacheTransactionManager} used by Apache Geode to manage local,
|
||||
* cache transactions.
|
||||
*
|
||||
* @return a reference to the {@link CacheTransactionManager}.
|
||||
* @see org.apache.geode.cache.CacheTransactionManager
|
||||
* @see #getCache()
|
||||
*/
|
||||
protected CacheTransactionManager getCacheTransactionManager() {
|
||||
return getCache().getCacheTransactionManager();
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
* 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;
|
||||
@@ -24,6 +23,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.gemfire.transaction.event.TransactionApplicationEvent;
|
||||
|
||||
/**
|
||||
* The {@link EnableGemfireCacheTransactions} annotation enables Pivotal GemFire or Apache Geode Cache Transactions
|
||||
@@ -49,4 +49,12 @@ import org.springframework.context.annotation.Import;
|
||||
@SuppressWarnings("unused")
|
||||
public @interface EnableGemfireCacheTransactions {
|
||||
|
||||
/**
|
||||
* Configures whether {@link TransactionApplicationEvent} objects are automatically fired by the framework.
|
||||
*
|
||||
* @return a boolean value indicating whether transactional events are automatically fired by the framework
|
||||
* without the need to manually publish transaction events. Defaults to {@literal false}.
|
||||
*/
|
||||
boolean enableAutoTransactionEventPublishing() default false;
|
||||
|
||||
}
|
||||
|
||||
@@ -13,14 +13,31 @@
|
||||
* 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 java.lang.annotation.Annotation;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.geode.cache.GemFireCache;
|
||||
import org.apache.geode.cache.TransactionListener;
|
||||
import org.apache.geode.cache.TransactionWriter;
|
||||
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.ImportAware;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.data.gemfire.CacheFactoryBean;
|
||||
import org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer;
|
||||
import org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer;
|
||||
import org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport;
|
||||
import org.springframework.data.gemfire.transaction.GemfireTransactionManager;
|
||||
import org.springframework.data.gemfire.transaction.event.ComposableTransactionWriter;
|
||||
import org.springframework.data.gemfire.transaction.event.TransactionListenerAdapter;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
@@ -30,16 +47,47 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.GemFireCache
|
||||
* @see org.apache.geode.cache.TransactionListener
|
||||
* @see org.apache.geode.cache.TransactionWriter
|
||||
* @see org.springframework.context.ApplicationEventPublisher
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.context.annotation.ImportAware
|
||||
* @see org.springframework.core.annotation.AnnotationAttributes
|
||||
* @see org.springframework.core.type.AnnotatedTypeMetadata
|
||||
* @see org.springframework.data.gemfire.CacheFactoryBean
|
||||
* @see org.springframework.data.gemfire.config.annotation.ClientCacheConfigurer
|
||||
* @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer
|
||||
* @see org.springframework.data.gemfire.config.annotation.support.AbstractAnnotationConfigSupport
|
||||
* @see org.springframework.data.gemfire.transaction.GemfireTransactionManager
|
||||
* @see org.springframework.data.gemfire.transaction.event.ComposableTransactionWriter
|
||||
* @see org.springframework.data.gemfire.transaction.event.TransactionListenerAdapter
|
||||
* @see org.springframework.transaction.annotation.EnableTransactionManagement
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
@SuppressWarnings("unused")
|
||||
public class GemfireCacheTransactionsConfiguration {
|
||||
public class GemfireCacheTransactionsConfiguration extends AbstractAnnotationConfigSupport implements ImportAware {
|
||||
|
||||
private volatile boolean enableAutoTransactionEventPublishing;
|
||||
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotationType() {
|
||||
return EnableGemfireCacheTransactions.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setImportMetadata(AnnotationMetadata annotationMetadata) {
|
||||
|
||||
if (isAnnotationPresent(annotationMetadata)) {
|
||||
|
||||
AnnotationAttributes enableGemfireCacheTransactionsAttributes = getAnnotationAttributes(annotationMetadata);
|
||||
|
||||
this.enableAutoTransactionEventPublishing =
|
||||
enableGemfireCacheTransactionsAttributes.getBoolean("enableAutoTransactionEventPublishing");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Declares and registers SDG's {@link GemfireTransactionManager} as the {@literal transactionManager}
|
||||
@@ -54,4 +102,67 @@ public class GemfireCacheTransactionsConfiguration {
|
||||
public GemfireTransactionManager transactionManager(GemFireCache gemfireCache) {
|
||||
return new GemfireTransactionManager(gemfireCache);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public ClientCacheConfigurer registerTransactionListenerAdapterClientCacheConfigurer(
|
||||
ApplicationEventPublisher applicationEventPublisher) {
|
||||
|
||||
return (beanName, bean) -> {
|
||||
|
||||
TransactionListenerAdapter transactionListener = newTransactionListenerAdapter(applicationEventPublisher);
|
||||
|
||||
registerGemFireCacheTransactionEventHandlers(bean, transactionListener);
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public PeerCacheConfigurer registerTransactionListenerAdapterPeerCacheConfigurer(
|
||||
ApplicationEventPublisher applicationEventPublisher) {
|
||||
|
||||
return (beanName, bean) -> {
|
||||
|
||||
TransactionListenerAdapter transactionListener = newTransactionListenerAdapter(applicationEventPublisher);
|
||||
|
||||
registerGemFireCacheTransactionEventHandlers(bean, transactionListener);
|
||||
};
|
||||
}
|
||||
|
||||
private TransactionListenerAdapter newTransactionListenerAdapter(
|
||||
ApplicationEventPublisher applicationEventPublisher) {
|
||||
|
||||
return new TransactionListenerAdapter(applicationEventPublisher);
|
||||
}
|
||||
|
||||
protected void registerGemFireCacheTransactionEventHandlers(CacheFactoryBean cacheFactoryBean,
|
||||
TransactionListenerAdapter transactionListener) {
|
||||
|
||||
if (this.enableAutoTransactionEventPublishing) {
|
||||
registerGemFireCacheTransactionListener(cacheFactoryBean, transactionListener);
|
||||
registerGemFireCacheTransactionWriter(cacheFactoryBean, transactionListener);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerGemFireCacheTransactionListener(CacheFactoryBean bean,
|
||||
TransactionListener transactionListener) {
|
||||
|
||||
List<TransactionListener> transactionListeners = new ArrayList<>(bean.getTransactionListeners());
|
||||
|
||||
transactionListeners.add(transactionListener);
|
||||
|
||||
bean.setTransactionListeners(transactionListeners);
|
||||
}
|
||||
|
||||
private void registerGemFireCacheTransactionWriter(CacheFactoryBean bean,
|
||||
TransactionWriter transactionWriter) {
|
||||
|
||||
TransactionWriter existingTransactionWriter = bean.getTransactionWriter();
|
||||
|
||||
TransactionWriter compositeTransactionWriter =
|
||||
ComposableTransactionWriter.compose(existingTransactionWriter, transactionWriter);
|
||||
|
||||
bean.setTransactionWriter(compositeTransactionWriter);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Copyright 2019 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
|
||||
*
|
||||
* https://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.event;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.apache.geode.cache.Cache;
|
||||
import org.apache.geode.cache.TransactionEvent;
|
||||
import org.apache.geode.cache.TransactionWriter;
|
||||
import org.apache.geode.cache.TransactionWriterException;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An implementation of Apache Geode's {@link TransactionWriter} interface that uses the {@literal Composite Software Design}
|
||||
* Pattern to compose multiple {@link TransactionWriter} objects into a single instance.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.TransactionWriter
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class ComposableTransactionWriter implements TransactionWriter {
|
||||
|
||||
/**
|
||||
* Factory method used to construct and compose 2 {@link TransactionWriter} objects into a composite instance of
|
||||
* {@link TransactionWriter} functioning as a single instance.
|
||||
*
|
||||
* @param transactionWriterOne first {@link TransactionWriter} in the composition.
|
||||
* @param transactionWriterTwo second {@link TransactionWriter} in the composition.
|
||||
* @return the first {@link TransactionWriter} if the second {@link TransactionWriter} is {@literal null}, or return
|
||||
* the second {@link TransactionWriter} if the first {@link TransactionWriter} is {@literal null}, or return
|
||||
* the composition of both {@link TransactionWriter} one and {@link TransactionWriter} two.
|
||||
* @see org.apache.geode.cache.TransactionWriter
|
||||
*/
|
||||
public static TransactionWriter compose(TransactionWriter transactionWriterOne,
|
||||
TransactionWriter transactionWriterTwo) {
|
||||
|
||||
return transactionWriterOne == null ? transactionWriterTwo
|
||||
: transactionWriterTwo == null ? transactionWriterOne
|
||||
: new ComposableTransactionWriter(transactionWriterOne, transactionWriterTwo);
|
||||
}
|
||||
|
||||
private final TransactionWriter transactionWriterOne;
|
||||
private final TransactionWriter transactionWriterTwo;
|
||||
|
||||
private ComposableTransactionWriter(TransactionWriter transactionWriterOne, TransactionWriter transactionWriterTwo) {
|
||||
|
||||
Assert.notNull(transactionWriterOne, "TransactionWriter one must not be null");
|
||||
Assert.notNull(transactionWriterTwo, "TransactionWriter two must not be null");
|
||||
|
||||
this.transactionWriterOne = transactionWriterOne;
|
||||
this.transactionWriterTwo = transactionWriterTwo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void beforeCommit(TransactionEvent event) throws TransactionWriterException {
|
||||
|
||||
this.transactionWriterOne.beforeCommit(event);
|
||||
this.transactionWriterTwo.beforeCommit(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
this.transactionWriterOne.close();
|
||||
this.transactionWriterTwo.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void init(Properties properties) {
|
||||
|
||||
this.transactionWriterOne.init(properties);
|
||||
this.transactionWriterTwo.init(properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void initialize(Cache cache, Properties properties) {
|
||||
|
||||
this.transactionWriterOne.initialize(cache, properties);
|
||||
this.transactionWriterTwo.initialize(cache, properties);
|
||||
}
|
||||
}
|
||||
@@ -37,12 +37,37 @@ public class TransactionApplicationEvent extends ApplicationEvent {
|
||||
|
||||
protected static final String TIMESTAMP_PATTERN = "yyyy-MM-dd-hh:mm:ss.S";
|
||||
|
||||
/**
|
||||
* Factory method used to construct a new instance of {@link TransactionApplicationEvent} initialized with
|
||||
* the given {@link Object source}.
|
||||
*
|
||||
* @param source {@link Object} defined as the source of this {@link TransactionApplicationEvent}.
|
||||
* @return a new instance of {@link TransactionApplicationEvent}.
|
||||
* @see #TransactionApplicationEvent(Object)
|
||||
*/
|
||||
public static TransactionApplicationEvent of(Object source) {
|
||||
return new TransactionApplicationEvent(source);
|
||||
}
|
||||
|
||||
private String details;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link TransactionApplicationEvent} initialized with the given {@link Object source}
|
||||
* of this transaction event.
|
||||
*
|
||||
* @param source {@link Object} defined as the source of this {@link TransactionApplicationEvent}.
|
||||
*/
|
||||
public TransactionApplicationEvent(Object source) {
|
||||
this(source, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new instance of {@link TransactionApplicationEvent} initialized with the given {@link Object source}
|
||||
* of this transaction event and {@link String details} describing the transaction event.
|
||||
*
|
||||
* @param source {@link Object} defined as the source of this {@link TransactionApplicationEvent}.
|
||||
* @param details {@link String} describing the transaction event.
|
||||
*/
|
||||
public TransactionApplicationEvent(Object source, String details) {
|
||||
|
||||
super(source);
|
||||
@@ -73,4 +98,9 @@ public class TransactionApplicationEvent extends ApplicationEvent {
|
||||
.map(details -> String.format("%s - %s", getTimestampAsString(), details))
|
||||
.orElse(String.format("%s[%s]", getClass().getSimpleName(), getTimestampAsString()));
|
||||
}
|
||||
|
||||
public TransactionApplicationEvent with(String details) {
|
||||
this.details = details;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2019 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
|
||||
*
|
||||
* https://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.event;
|
||||
|
||||
import org.apache.geode.cache.TransactionEvent;
|
||||
import org.apache.geode.cache.TransactionListener;
|
||||
import org.apache.geode.cache.TransactionWriter;
|
||||
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The {@link TransactionListenerAdapter} class is an Apache Geode {@link TransactionListener}
|
||||
* and {@link TransactionWriter} implementation that publishes the {@link TransactionEvent} to application components
|
||||
* and beans declared in the Spring {@link ApplicationContext} using the {@link ApplicationEventPublisher}.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.apache.geode.cache.TransactionEvent
|
||||
* @see org.apache.geode.cache.TransactionListener
|
||||
* @see org.apache.geode.cache.TransactionWriter
|
||||
* @see org.springframework.context.ApplicationContext
|
||||
* @see org.springframework.context.ApplicationEventPublisher
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class TransactionListenerAdapter implements TransactionListener, TransactionWriter {
|
||||
|
||||
private final ApplicationEventPublisher applicationEventPublisher;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of the {@link TransactionListenerAdapter} initialized with the required
|
||||
* {@link ApplicationEventPublisher} to publish Apache Geode cache {@link TransactionEvent TransactionEvents}
|
||||
* to application declared components and beans in a Spring {@link ApplicationContext}.
|
||||
*
|
||||
* @param applicationEventPublisher {@link ApplicationEventPublisher} used to publish Apache Geode cache
|
||||
* {@link TransactionEvent TransactionEvents}.
|
||||
* @throws IllegalArgumentException if the {@link ApplicationEventPublisher} is {@literal null}.
|
||||
* @see org.springframework.context.ApplicationEventPublisher
|
||||
*/
|
||||
public TransactionListenerAdapter(ApplicationEventPublisher applicationEventPublisher) {
|
||||
|
||||
Assert.notNull(applicationEventPublisher, "ApplicationEventPublisher must not be null");
|
||||
|
||||
this.applicationEventPublisher = applicationEventPublisher;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void beforeCommit(TransactionEvent event) {
|
||||
|
||||
// NOTE: this will not work because Apache Geode's cache before commit transaction event is only triggered
|
||||
// after Spring's AbstractPlatformTransaction.triggerBeforeCommit(:TransactionStatus) method, which is where
|
||||
// all application @TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT) annotated transaction
|
||||
// event handler methods are invoked.
|
||||
|
||||
//this.applicationEventPublisher.publishEvent(TransactionApplicationEvent.of(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void afterCommit(TransactionEvent event) {
|
||||
this.applicationEventPublisher.publishEvent(TransactionApplicationEvent.of(event));
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void afterFailedCommit(TransactionEvent event) { }
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
@Override
|
||||
public void afterRollback(TransactionEvent event) {
|
||||
this.applicationEventPublisher.publishEvent(TransactionApplicationEvent.of(event));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
/*
|
||||
* Copyright 2019 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.gemfire.mapping.GemfireMappingContext;
|
||||
import org.springframework.data.gemfire.mapping.annotation.Region;
|
||||
import org.springframework.data.gemfire.repository.support.GemfireRepositoryFactoryBean;
|
||||
import org.springframework.data.gemfire.transaction.event.TransactionApplicationEvent;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
|
||||
/**
|
||||
* {@link AbstractTransactionalEventListenerIntegrationTests} is an abstract base class for writing transactional,
|
||||
* Apache Geode cache event listener tests.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.springframework.context.ApplicationEventPublisher
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Configuration
|
||||
* @see org.springframework.data.gemfire.transaction.event.TransactionApplicationEvent
|
||||
* @see org.springframework.transaction.annotation.Transactional
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public abstract class AbstractTransactionalEventListenerIntegrationTests {
|
||||
|
||||
protected static final String GEMFIRE_LOG_LEVEL = "error";
|
||||
|
||||
@Autowired
|
||||
private CustomerService customerService;
|
||||
|
||||
protected abstract void assertTransactionEventListenerOnSuccess() throws Exception;
|
||||
|
||||
protected abstract void assertTransactionEventListenerOnFailure() throws Exception;
|
||||
|
||||
@Test
|
||||
public void successfulEntityTransactionTriggersCommitTransactionEvents() throws Exception {
|
||||
|
||||
Customer jonDoe = this.customerService.save(Customer.newCustomer(1L, "Jon Doe"));
|
||||
|
||||
Customer jonDoeLoaded = this.customerService.findById(jonDoe.getId());
|
||||
|
||||
assertThat(jonDoeLoaded).isEqualTo(jonDoe);
|
||||
assertTransactionEventListenerOnSuccess();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void failingEntityTransactionTriggersRollbackTransactionEvent() throws Exception {
|
||||
|
||||
Customer janeDoe = Customer.newCustomer(2L, "Jane Doe");
|
||||
|
||||
try {
|
||||
this.customerService.saveFailsAndRollsback(janeDoe);
|
||||
}
|
||||
catch (IllegalStateException expected) {
|
||||
|
||||
assertTransactionEventListenerOnFailure();
|
||||
assertThat(expected).hasMessage("TEST");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
try {
|
||||
this.customerService.findById(janeDoe.getId());
|
||||
}
|
||||
catch (IllegalStateException alsoExpected) {
|
||||
|
||||
assertThat(alsoExpected).hasMessage("No Customer having ID [2] was found");
|
||||
assertThat(alsoExpected).hasNoCause();
|
||||
|
||||
throw alsoExpected;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@Region("Customers")
|
||||
@ToString(of = "name")
|
||||
@RequiredArgsConstructor(staticName = "newCustomer")
|
||||
static class Customer implements Serializable {
|
||||
|
||||
@Id @NonNull
|
||||
private Long id;
|
||||
|
||||
@NonNull
|
||||
private String name;
|
||||
|
||||
}
|
||||
|
||||
public interface CustomerRepository extends CrudRepository<Customer, Long> { }
|
||||
|
||||
@Configuration
|
||||
static class CustomerRepositoryConfiguration {
|
||||
|
||||
@Bean
|
||||
GemfireRepositoryFactoryBean<CustomerRepository, Customer, Long> customerRepositoryFactoryBean() {
|
||||
|
||||
GemfireRepositoryFactoryBean<CustomerRepository, Customer, Long> customerRepositoryFactoryBean
|
||||
= new GemfireRepositoryFactoryBean<>(CustomerRepository.class);
|
||||
|
||||
customerRepositoryFactoryBean.setGemfireMappingContext(new GemfireMappingContext());
|
||||
|
||||
return customerRepositoryFactoryBean;
|
||||
}
|
||||
}
|
||||
|
||||
@Service
|
||||
public static class CustomerService {
|
||||
|
||||
private final CustomerRepository customerRepository;
|
||||
|
||||
public CustomerService(CustomerRepository customerRepository) {
|
||||
|
||||
Assert.notNull(customerRepository, "CustomerRepository is required");
|
||||
|
||||
this.customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public Customer findById(Long id) {
|
||||
return this.customerRepository.findById(id)
|
||||
.orElseThrow(() -> newIllegalStateException("No Customer having ID [%d] was found", id));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Customer save(Customer customer) {
|
||||
return this.customerRepository.save(customer);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Customer saveFailsAndRollsback(Customer customer) {
|
||||
|
||||
this.customerRepository.save(customer);
|
||||
|
||||
throw newIllegalStateException("TEST");
|
||||
}
|
||||
}
|
||||
|
||||
@Service
|
||||
public static class TransactionEventPublishingCustomerService extends CustomerService {
|
||||
|
||||
private final ApplicationEventPublisher eventPublisher;
|
||||
|
||||
public TransactionEventPublishingCustomerService(ApplicationEventPublisher eventPublisher,
|
||||
CustomerRepository customerRepository) {
|
||||
|
||||
super(customerRepository);
|
||||
|
||||
Assert.notNull(eventPublisher, "ApplicationEventPublisher is required");
|
||||
|
||||
this.eventPublisher = eventPublisher;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Customer save(Customer customer) {
|
||||
|
||||
customer = super.save(customer);
|
||||
|
||||
this.eventPublisher.publishEvent(new TransactionApplicationEvent(this,
|
||||
String.valueOf(customer.getId())));
|
||||
|
||||
return customer;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Customer saveFailsAndRollsback(Customer customer) {
|
||||
|
||||
this.eventPublisher.publishEvent(new TransactionApplicationEvent(this,
|
||||
String.valueOf(customer.getId())));
|
||||
|
||||
return super.saveFailsAndRollsback(customer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
/*
|
||||
* Copyright 2019 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
|
||||
*
|
||||
* https://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;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.EventListener;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.apache.geode.cache.RegionShortcut;
|
||||
import org.apache.geode.cache.TransactionEvent;
|
||||
import org.apache.geode.cache.TransactionWriter;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions;
|
||||
import org.springframework.data.gemfire.config.annotation.PeerCacheApplication;
|
||||
import org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer;
|
||||
import org.springframework.data.gemfire.transaction.config.EnableGemfireCacheTransactions;
|
||||
import org.springframework.data.gemfire.transaction.event.TransactionApplicationEvent;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.transaction.event.TransactionPhase;
|
||||
import org.springframework.transaction.event.TransactionalEventListener;
|
||||
|
||||
/**
|
||||
* Integration Tests for the Spring {@link TransactionalEventListener} in the context of Apache Geode
|
||||
* cache transactions when auto-publishing of transaction events is enabled.
|
||||
*
|
||||
* @author John Blum
|
||||
* @see org.junit.Test
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see org.springframework.data.gemfire.config.annotation.PeerCacheApplication
|
||||
* @see org.springframework.data.gemfire.config.annotation.PeerCacheConfigurer
|
||||
* @see org.springframework.data.gemfire.transaction.config.EnableGemfireCacheTransactions
|
||||
* @see org.springframework.data.gemfire.transaction.event.TransactionApplicationEvent
|
||||
* @see org.springframework.test.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.junit4.SpringRunner
|
||||
* @see org.springframework.transaction.event.TransactionPhase
|
||||
* @see org.springframework.transaction.event.TransactionalEventListener
|
||||
* @since 2.3.0
|
||||
*/
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration
|
||||
@SuppressWarnings("unused")
|
||||
public class AutoTransactionalEventListenerIntegrationTests extends AbstractTransactionalEventListenerIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private TestTransactionEventListener transactionEventListener;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("MockTransactionWriter")
|
||||
private TransactionWriter mockTransactionWriter;
|
||||
|
||||
@Override
|
||||
protected void assertTransactionEventListenerOnSuccess() throws Exception {
|
||||
|
||||
assertThat(this.transactionEventListener.isBeforeCommitInvoked()).isFalse();
|
||||
|
||||
assertThat(this.transactionEventListener.getAndClearTransactionPhases())
|
||||
.containsExactly(TransactionPhase.AFTER_COMMIT);
|
||||
|
||||
verify(this.mockTransactionWriter, times(1)).beforeCommit(any(TransactionEvent.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void assertTransactionEventListenerOnFailure() {
|
||||
|
||||
assertThat(this.transactionEventListener.isBeforeCommitInvoked()).isFalse();
|
||||
|
||||
assertThat(this.transactionEventListener.getAndClearTransactionPhases())
|
||||
.containsExactly(TransactionPhase.AFTER_ROLLBACK);
|
||||
}
|
||||
|
||||
@PeerCacheApplication(logLevel = GEMFIRE_LOG_LEVEL)
|
||||
@EnableEntityDefinedRegions(
|
||||
basePackageClasses = Customer.class,
|
||||
serverRegionShortcut = RegionShortcut.LOCAL
|
||||
)
|
||||
@EnableGemfireCacheTransactions(enableAutoTransactionEventPublishing = true)
|
||||
@Import(CustomerRepositoryConfiguration.class)
|
||||
static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
CustomerService customerService(CustomerRepository customerRepository) {
|
||||
return new CustomerService(customerRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
TestTransactionEventListener testTransactionEventListener() {
|
||||
return new TestTransactionEventListener();
|
||||
}
|
||||
|
||||
@Bean("MockTransactionWriter")
|
||||
TransactionWriter mockTransactionWriter() {
|
||||
return mock(TransactionWriter.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
PeerCacheConfigurer transactionWriterRegisteringCacheConfigurer(
|
||||
@Qualifier("MockTransactionWriter") TransactionWriter transactionWriter) {
|
||||
|
||||
return (beanName, bean) -> bean.setTransactionWriter(transactionWriter);
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class TestTransactionEventListener implements EventListener {
|
||||
|
||||
private AtomicBoolean beforeCommitInvoked = new AtomicBoolean(false);
|
||||
|
||||
private List<TransactionPhase> transactionPhases = new ArrayList<>();
|
||||
|
||||
public List<TransactionPhase> getAndClearTransactionPhases() {
|
||||
|
||||
List<TransactionPhase> copy = new ArrayList<>(this.transactionPhases);
|
||||
|
||||
this.transactionPhases.clear();
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
private boolean isBeforeCommitInvoked() {
|
||||
return this.beforeCommitInvoked.getAndSet(false);
|
||||
}
|
||||
|
||||
private void handleTransactionEvent(TransactionApplicationEvent event, TransactionPhase transactionPhase) {
|
||||
|
||||
Optional.ofNullable(event)
|
||||
.map(TransactionApplicationEvent::getSource)
|
||||
.filter(TransactionEvent.class::isInstance)
|
||||
.ifPresent(transactionEvent -> this.transactionPhases.add(transactionPhase));
|
||||
}
|
||||
|
||||
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
|
||||
public void handleTransactionBeforeCommit(TransactionApplicationEvent event) {
|
||||
this.beforeCommitInvoked.set(true);
|
||||
handleTransactionEvent(event, TransactionPhase.BEFORE_COMMIT);
|
||||
}
|
||||
|
||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
||||
public void handleTransactionAfterCommit(TransactionApplicationEvent event) {
|
||||
handleTransactionEvent(event, TransactionPhase.AFTER_COMMIT);
|
||||
}
|
||||
|
||||
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
|
||||
public void handleTransactionAfterRollback(TransactionApplicationEvent event) {
|
||||
handleTransactionEvent(event, TransactionPhase.AFTER_ROLLBACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,16 +16,13 @@
|
||||
package org.springframework.data.gemfire.transaction;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.data.gemfire.util.RuntimeExceptionFactory.newIllegalStateException;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import org.apache.geode.cache.client.ClientRegionShortcut;
|
||||
@@ -34,28 +31,16 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationEvent;
|
||||
import org.springframework.context.ApplicationEventPublisher;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
|
||||
import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions;
|
||||
import org.springframework.data.gemfire.mapping.GemfireMappingContext;
|
||||
import org.springframework.data.gemfire.mapping.annotation.Region;
|
||||
import org.springframework.data.gemfire.repository.support.GemfireRepositoryFactoryBean;
|
||||
import org.springframework.data.gemfire.transaction.config.EnableGemfireCacheTransactions;
|
||||
import org.springframework.data.gemfire.transaction.event.TransactionApplicationEvent;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.event.TransactionPhase;
|
||||
import org.springframework.transaction.event.TransactionalEventListener;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
/**
|
||||
* Integration Tests for the Spring {@link TransactionalEventListener} in the context of Apache Geode
|
||||
@@ -66,9 +51,11 @@ import lombok.RequiredArgsConstructor;
|
||||
* @see org.springframework.context.ApplicationEvent
|
||||
* @see org.springframework.context.ApplicationEventPublisher
|
||||
* @see org.springframework.context.annotation.Bean
|
||||
* @see org.springframework.context.annotation.Import
|
||||
* @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication
|
||||
* @see TransactionApplicationEvent
|
||||
* @see org.springframework.data.gemfire.transaction.AbstractTransactionalEventListenerIntegrationTests
|
||||
* @see org.springframework.data.gemfire.transaction.config.EnableGemfireCacheTransactions
|
||||
* @see org.springframework.data.gemfire.transaction.event.TransactionApplicationEvent
|
||||
* @see org.springframework.test.context.ContextConfiguration
|
||||
* @see org.springframework.test.context.junit4.SpringRunner
|
||||
* @see org.springframework.transaction.annotation.Transactional
|
||||
@@ -79,24 +66,13 @@ import lombok.RequiredArgsConstructor;
|
||||
@RunWith(SpringRunner.class)
|
||||
@ContextConfiguration
|
||||
@SuppressWarnings("unused")
|
||||
public class TransactionalEventListenerIntegrationTests {
|
||||
|
||||
private static final String GEMFIRE_LOG_LEVEL = "error";
|
||||
|
||||
@Autowired
|
||||
private CustomerService customerService;
|
||||
public class TransactionalEventListenerIntegrationTests extends AbstractTransactionalEventListenerIntegrationTests {
|
||||
|
||||
@Autowired
|
||||
private TestTransactionEventListener transactionEventListener;
|
||||
|
||||
@Test
|
||||
public void successfulEntityTransactionTriggersCommitTransactionEvents() {
|
||||
|
||||
Customer jonDoe = this.customerService.save(Customer.newCustomer(1L, "Jon Doe"));
|
||||
|
||||
Customer jonDoeLoaded = this.customerService.findById(jonDoe.getId());
|
||||
|
||||
assertThat(jonDoeLoaded).isEqualTo(jonDoe);
|
||||
@Override
|
||||
protected void assertTransactionEventListenerOnSuccess() {
|
||||
|
||||
assertThat(this.transactionEventListener.getAndClearTransactionDetails()).containsExactly("1");
|
||||
|
||||
@@ -104,35 +80,13 @@ public class TransactionalEventListenerIntegrationTests {
|
||||
.containsExactly(TransactionPhase.BEFORE_COMMIT, TransactionPhase.AFTER_COMMIT);
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void failingEntityTransactionTriggersRollbackTransactionEvent() {
|
||||
@Override
|
||||
protected void assertTransactionEventListenerOnFailure() {
|
||||
|
||||
Customer janeDoe = Customer.newCustomer(2L, "Jane Doe");
|
||||
assertThat(this.transactionEventListener.getAndClearTransactionDetails()).containsExactly("2");
|
||||
|
||||
try {
|
||||
this.customerService.saveFailsAndRollsback(janeDoe);
|
||||
}
|
||||
catch (IllegalStateException expected) {
|
||||
|
||||
assertThat(this.transactionEventListener.getAndClearTransactionDetails()).containsExactly("2");
|
||||
|
||||
assertThat(this.transactionEventListener.getAndClearTransactionPhases())
|
||||
.containsExactly(TransactionPhase.AFTER_ROLLBACK);
|
||||
|
||||
assertThat(expected).hasMessage("TEST");
|
||||
assertThat(expected).hasNoCause();
|
||||
|
||||
try {
|
||||
this.customerService.findById(janeDoe.getId());
|
||||
}
|
||||
catch (IllegalStateException alsoExpected) {
|
||||
|
||||
assertThat(alsoExpected).hasMessage("No Customer having ID [2] was found");
|
||||
assertThat(alsoExpected).hasNoCause();
|
||||
|
||||
throw alsoExpected;
|
||||
}
|
||||
}
|
||||
assertThat(this.transactionEventListener.getAndClearTransactionPhases())
|
||||
.containsExactly(TransactionPhase.AFTER_ROLLBACK);
|
||||
}
|
||||
|
||||
@ClientCacheApplication(logLevel = GEMFIRE_LOG_LEVEL)
|
||||
@@ -141,24 +95,14 @@ public class TransactionalEventListenerIntegrationTests {
|
||||
clientRegionShortcut = ClientRegionShortcut.LOCAL
|
||||
)
|
||||
@EnableGemfireCacheTransactions
|
||||
@Import(CustomerRepositoryConfiguration.class)
|
||||
static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
GemfireRepositoryFactoryBean<CustomerRepository, Customer, Long> customerRepositoryFactoryBean() {
|
||||
|
||||
GemfireRepositoryFactoryBean<CustomerRepository, Customer, Long> customerRepositoryFactoryBean
|
||||
= new GemfireRepositoryFactoryBean<>(CustomerRepository.class);
|
||||
|
||||
customerRepositoryFactoryBean.setGemfireMappingContext(new GemfireMappingContext());
|
||||
|
||||
return customerRepositoryFactoryBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
CustomerService customerService(ApplicationEventPublisher eventPublisher,
|
||||
CustomerRepository customerRepository) {
|
||||
|
||||
return new CustomerService(eventPublisher, customerRepository);
|
||||
return new TransactionEventPublishingCustomerService(eventPublisher, customerRepository);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@@ -167,66 +111,6 @@ public class TransactionalEventListenerIntegrationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode
|
||||
@Region("Customers")
|
||||
@RequiredArgsConstructor(staticName = "newCustomer")
|
||||
static class Customer implements Serializable {
|
||||
|
||||
@Id @NonNull
|
||||
private Long id;
|
||||
|
||||
@NonNull
|
||||
private String name;
|
||||
|
||||
}
|
||||
|
||||
public interface CustomerRepository extends CrudRepository<Customer, Long> { }
|
||||
|
||||
@Service
|
||||
public static class CustomerService {
|
||||
|
||||
private ApplicationEventPublisher eventPublisher;
|
||||
|
||||
private final CustomerRepository customerRepository;
|
||||
|
||||
public CustomerService(ApplicationEventPublisher eventPublisher, CustomerRepository customerRepository) {
|
||||
|
||||
Assert.notNull(eventPublisher, "ApplicationEventPublisher is required");
|
||||
Assert.notNull(customerRepository, "CustomerRepository is required");
|
||||
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.customerRepository = customerRepository;
|
||||
}
|
||||
|
||||
public Customer findById(Long id) {
|
||||
return this.customerRepository.findById(id)
|
||||
.orElseThrow(() -> newIllegalStateException("No Customer having ID [%d] was found", id));
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Customer save(Customer customer) {
|
||||
|
||||
customer = this.customerRepository.save(customer);
|
||||
|
||||
this.eventPublisher.publishEvent(new TransactionApplicationEvent(this,
|
||||
String.valueOf(customer.getId())));
|
||||
|
||||
return customer;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public Customer saveFailsAndRollsback(Customer customer) {
|
||||
|
||||
this.customerRepository.save(customer);
|
||||
|
||||
this.eventPublisher.publishEvent(new TransactionApplicationEvent(this,
|
||||
String.valueOf(customer.getId())));
|
||||
|
||||
throw newIllegalStateException("TEST");
|
||||
}
|
||||
}
|
||||
|
||||
@Component
|
||||
public static class TestTransactionEventListener {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user