INT-2727 PseudoTX Refactoring

Remove the need for pseudo-transactional element

INT-2727 PseudoTX
Add PseudoTransactionalTransactionManager

INT-2727
addressed PR comments
cherry picked previous code for mail module to eliminate breaking change

INT-2727
initial refactoring pseudo-tx support to use common configuration

INT-2727
finalizing pseudo-tx synchronization support

INT-2727 polishing

INT-2727 polishing based on PR comments

INT-2727 addressed PR comments

INT-2727 polishing

INT-2727 Remove PseudoTransactionalMessageSource

Instead of getResource, bind the resource holder before
receive() and then add attributes to the holder.

INT-2727 polishing

INT-2727 Polishing

Remove bind of #resource; add beforeCommit() test;
add TransactionTemplate tests.
This commit is contained in:
Oleg Zhurakousky
2012-08-27 13:16:21 -04:00
committed by Gary Russell
parent 5828f70321
commit 94cc5a73e2
50 changed files with 1523 additions and 1357 deletions

View File

@@ -29,9 +29,11 @@ import org.springframework.expression.Expression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.Message;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.core.PseudoTransactionalMessageSource;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.transaction.MessageSourceResourceHolder;
import org.springframework.integration.util.ExpressionUtils;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
/**
* Inbound channel adapter which returns a Message representing a view into
@@ -43,7 +45,7 @@ import org.springframework.util.Assert;
* @since 2.2
*/
public class RedisStoreMessageSource extends IntegrationObjectSupport
implements PseudoTransactionalMessageSource<RedisStore, RedisStore> {
implements MessageSource<RedisStore> {
private final ThreadLocal<RedisStore> resourceHolder = new ThreadLocal<RedisStore>();
@@ -117,7 +119,12 @@ public class RedisStoreMessageSource extends IntegrationObjectSupport
Assert.hasText(key, "Failed to determine the key for the collection");
RedisStore store = this.createStoreView(key);
this.resourceHolder.set(store);
Object holder = TransactionSynchronizationManager.getResource(this);
if (holder != null) {
Assert.isInstanceOf(MessageSourceResourceHolder.class, holder);
((MessageSourceResourceHolder) holder).addAttribute("store", store);
}
if (store instanceof Collection<?> && ((Collection<Object>)store).size() < 1){
return null;
@@ -158,12 +165,4 @@ public class RedisStoreMessageSource extends IntegrationObjectSupport
public void afterRollback(Object object) {
this.resourceHolder.remove();
}
public void afterReceiveNoTx(RedisStore resource) {
this.resourceHolder.remove();
}
public void afterSendNoTx(RedisStore resource) {
this.resourceHolder.remove();
}
}

View File

@@ -168,18 +168,6 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="error-channel" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<xsd:documentation>
Channel to which Error Messages will be sent.
</xsd:documentation>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.MessageChannel"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="message-converter" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
@@ -193,6 +181,16 @@
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="error-channel" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Identifies channel that error messages will be sent to if a failure occurs in this
gateway's invocation. If no "error-channel" reference is provided, this gateway will
propagate Exceptions to the caller. To completely suppress Exceptions, provide a
reference to the "nullChannel" here.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>

View File

@@ -18,16 +18,24 @@ package org.springframework.integration.redis.config;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import org.junit.Test;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.BoundListOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.data.redis.support.collections.RedisList;
import org.springframework.data.redis.support.collections.RedisZSet;
import org.springframework.integration.Message;
import org.springframework.integration.MessagingException;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.MessageHandler;
import org.springframework.integration.core.SubscribableChannel;
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
import org.springframework.integration.redis.rules.RedisAvailable;
import org.springframework.integration.redis.rules.RedisAvailableTests;
@@ -85,6 +93,42 @@ public class RedisCollectionsInboundChannelAdapterParserTests extends RedisAvail
context.close();
}
@Test
@RedisAvailable
public void testListInboundConfigurationWithSynchronizationAndRollback() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
this.prepareList(jcf);
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<Object, Object>();
redisTemplate.setConnectionFactory(jcf);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
BoundListOperations<Object, Object> ops = redisTemplate.boundListOps("baz");
assertTrue(ops.size() == 0);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("list-inbound-adapter.xml", this.getClass());
SourcePollingChannelAdapter spca = context.getBean("listAdapterWithSynchronizationAndRollback", SourcePollingChannelAdapter.class);
SubscribableChannel redisFailChannel = context.getBean("redisFailChannel", SubscribableChannel.class);
redisFailChannel.subscribe(new MessageHandler() {
public void handleMessage(Message<?> message) throws MessagingException {
throw new MessagingException("intentional");
}
});
spca.start();
Thread.sleep(1000);
ops = redisTemplate.boundListOps("baz");
assertTrue(ops.size() == 13);
ops = redisTemplate.boundListOps("bar");
assertTrue(ops.size() == 0);
spca.stop();
context.close();
}
@Test
@RedisAvailable
@SuppressWarnings("unchecked")
@@ -109,6 +153,27 @@ public class RedisCollectionsInboundChannelAdapterParserTests extends RedisAvail
context.close();
}
@Test
@RedisAvailable
@SuppressWarnings("unchecked")
public void testListInboundConfigurationWithBeforeCommit() throws Exception{
JedisConnectionFactory jcf = this.getConnectionFactoryForTest();
this.prepareList(jcf);
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("list-inbound-adapter.xml", this.getClass());
SourcePollingChannelAdapter spca = context.getBean("listAdapterWithSynchronizationBeforeCommit", SourcePollingChannelAdapter.class);
spca.start();
QueueChannel redisChannel = context.getBean("redisChannel", QueueChannel.class);
QueueChannel adapterErrors = context.getBean("adapterErrors", QueueChannel.class);
Message<RedisList<Object>> message = (Message<RedisList<Object>>) redisChannel.receive(1000);
assertNotNull(message);
assertNotNull(adapterErrors.receive(2000));
spca.stop();
context.close();
}
@Test
@RedisAvailable
@SuppressWarnings("unchecked")

View File

@@ -54,7 +54,7 @@ public class RedisInboundChannelAdapterParserTests extends RedisAvailableTests{
@Autowired @Qualifier("autoChannel.adapter")
private RedisInboundChannelAdapter autoChannelAdapter;
@Test
@Test
@RedisAvailable
public void validateConfiguration() {
RedisInboundChannelAdapter adapter = context.getBean("adapter", RedisInboundChannelAdapter.class);
@@ -67,7 +67,7 @@ public class RedisInboundChannelAdapterParserTests extends RedisAvailableTests{
assertEquals(converterBean, accessor.getPropertyValue("messageConverter"));
}
@Test
@Test
@RedisAvailable
public void testInboundChannelAdapterMessaging() throws Exception{
JedisConnectionFactory connectionFactory = new JedisConnectionFactory();

View File

@@ -22,9 +22,21 @@
channel="redisChannel"
auto-startup="false">
<int:poller fixed-rate="2000" max-messages-per-poll="10">
<int:pseudo-transactional on-success-expression="#resource.rename('bar')"/>
<int:transactional synchronization-factory="syncFactory"/>
</int:poller>
</int-redis:store-inbound-channel-adapter>
<int-redis:store-inbound-channel-adapter id="listAdapterWithSynchronizationAndRollback"
connection-factory="redisConnectionFactory"
key-expression="'presidents'"
channel="redisFailChannel"
auto-startup="false">
<int:poller fixed-rate="2000" max-messages-per-poll="10">
<int:transactional synchronization-factory="syncFactory"/>
</int:poller>
</int-redis:store-inbound-channel-adapter>
<int:channel id="redisFailChannel"/>
<int-redis:store-inbound-channel-adapter id="listAdapterWithSynchronizationAndRedisTemplate"
redis-template="redisTemplate"
@@ -32,9 +44,32 @@
channel="redisChannel"
auto-startup="false">
<int:poller fixed-rate="2000" max-messages-per-poll="10">
<int:pseudo-transactional on-success-expression="#resource.rename('bar')"/>
<int:transactional synchronization-factory="syncFactory"/>
</int:poller>
</int-redis:store-inbound-channel-adapter>
<int:transaction-synchronization-factory id="syncFactory">
<int:after-commit expression="#resource.attributes['store'].rename('bar')"/>
<int:after-rollback expression="#store.rename('baz')"/>
</int:transaction-synchronization-factory>
<int-redis:store-inbound-channel-adapter id="listAdapterWithSynchronizationBeforeCommit"
redis-template="redisTemplate"
key-expression="'presidents'"
channel="redisChannel"
auto-startup="false">
<int:poller fixed-rate="2000" max-messages-per-poll="10" error-channel="adapterErrors">
<int:transactional synchronization-factory="syncFactory2"/>
</int:poller>
</int-redis:store-inbound-channel-adapter>
<int:channel id="adapterErrors">
<int:queue/>
</int:channel>
<int:transaction-synchronization-factory id="syncFactory2">
<int:before-commit expression="5/0"/>
</int:transaction-synchronization-factory>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="keySerializer">
@@ -53,5 +88,7 @@
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="port" value="7379"/>
</bean>
<bean id="transactionManager" class="org.springframework.integration.transaction.PseudoTransactionManager"/>
</beans>

View File

@@ -41,9 +41,13 @@
auto-startup="false"
collection-type="ZSET">
<int:poller fixed-rate="1000" max-messages-per-poll="2">
<int:pseudo-transactional on-success-expression="#resource.removeByScore(18, 18)" on-success-result-channel="nullChannel"/>
<int:transactional synchronization-factory="syncFactory"/>
</int:poller>
</int-redis:store-inbound-channel-adapter>
<int:transaction-synchronization-factory id="syncFactory">
<int:after-commit expression="#resource.attributes['store'].removeByScore(18, 18)"/>
</int:transaction-synchronization-factory>
<int:channel id="redisChannel">
<int:queue/>
@@ -56,5 +60,7 @@
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="port" value="7379"/>
</bean>
<bean id="transactionManager" class="org.springframework.integration.transaction.PseudoTransactionManager"/>
</beans>