INT-2685 Transaction Synchronization

Remove M3 disposition-* attributes on File/(S)FTP inbound adapters.

Add <pseudo-transactional/> and <transaction-synchronization/> elements
to <poller/>.

These elements provide the following attributes:

* on-success-expression
* on-success-result-channel
* on-failure-expression
* on-failure-result-channel
* send-timeout

<transaction-synchronization/> synchronizes these expression evaluations
with the transaction, such that they are executed immediately after
the commit/rollback.

<psuedo-transactional/> is used for a non-transactional poller.

When an <advice-chain/> is provided to the poller, <psuedo-transactional/>
and <transaction-synchronization/> are synonyms; and the behavior is
dictated by whether or not the <advice-chain/> contains a transaction
advice. It is recommended that <pseudo-transactional/> is used when the
<advice-chain/> does not have a txAdvice, and <transaction-synchronization/>
when it does, but the framework does not enforce this.

The expressions have the original (polled) message as the #root variable.
In addition, a BeanResolver is provided, allowing expressions such as
'@someBean.handleSuccess(payload)'.

MessageSources may also implement PseudoTransactionalMessageSource. This
has a number of methods allowing more flexibility in transactional and
non-transactional environents. For example, for backwards compatibility.
the mail-inbound-channel-adapter deletes its polled message after the
receive() rather than after the polled message is sent (when running in
a non-transactional poller). However, when running in a transactional
poller, the delete is done after the transaction commits (but not when
it rolls back).

In addition, MessageSources that implement this interface can optionally
provide an arbitrary object to the success/failure expressions in a
variable named '#resource'.

INT-2685 Polishing

PR Review Comments

Add tests for non-tx PseudoTransactionalMessageSource
This commit is contained in:
Gary Russell
2012-07-26 18:23:49 -04:00
committed by Oleg Zhurakousky
parent d6623bda82
commit 4de02fa75e
33 changed files with 892 additions and 458 deletions

View File

@@ -140,24 +140,4 @@ public class PollerParserTests {
"pollerWithCronAndFixedDelay.xml", PollerParserTests.class);
}
@Test
public void pollerWithSync() {
ApplicationContext context = new ClassPathXmlApplicationContext(
"pollerWithSynchronization.xml", PollerParserTests.class);
Object poller = context.getBean("noSync");
assertNotNull(poller);
PollerMetadata metadata = (PollerMetadata) poller;
assertEquals(true, metadata.isSynchronized());
poller = context.getBean("syncTrue");
assertNotNull(poller);
metadata = (PollerMetadata) poller;
assertEquals(true, metadata.isSynchronized());
poller = context.getBean("syncFalse");
assertNotNull(poller);
metadata = (PollerMetadata) poller;
assertEquals(false, metadata.isSynchronized());
}
}

View File

@@ -15,13 +15,17 @@
*/
package org.springframework.integration.endpoint;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.integration.Message;
import org.springframework.integration.MessageHeaders;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.core.PseudoTransactionalMessageSource;
import org.springframework.integration.message.GenericMessage;
@@ -76,9 +80,88 @@ public class PseudoTransactionalMessageSourceTests {
assertSame(object, committed.get());
TransactionSynchronizationUtils.triggerAfterCompletion(TransactionSynchronization.STATUS_COMMITTED);
TransactionSynchronizationManager.clearSynchronization();
TransactionSynchronizationManager.setActualTransactionActive(false);
assertNull(rolledBack.get());
}
@Test
public void testPseudoCommitWithMessage() {
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
QueueChannel outputChannel = new QueueChannel();
adapter.setOutputChannel(outputChannel);
final Object object = new Object();
final AtomicReference<Object> afterReceive = new AtomicReference<Object>();
final AtomicReference<Object> afterSend = new AtomicReference<Object>();
adapter.setSource(new PseudoTransactionalMessageSource<String, Object>() {
public Message<String> receive() {
return new GenericMessage<String>("foo");
}
public Object getResource() {
return object;
}
public void afterCommit(Object resource) {
throw new RuntimeException("no tx - commit not expected");
}
public void afterRollback(Object resource) {
throw new RuntimeException("no tx - rollback not expected");
}
public void afterReceiveNoTx(Object resource) {
afterReceive.set(resource);
}
public void afterSendNoTx(Object resource) {
afterSend.set(resource);
}
});
adapter.doPoll();
assertSame(object, afterReceive.get());
assertSame(object, afterSend.get());
}
@Test
public void testPseudoCommitNoMessage() {
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
QueueChannel outputChannel = new QueueChannel();
adapter.setOutputChannel(outputChannel);
final Object object = new Object();
final AtomicReference<Object> afterReceive = new AtomicReference<Object>();
adapter.setSource(new PseudoTransactionalMessageSource<String, Object>() {
public Message<String> receive() {
return null;
}
public Object getResource() {
return object;
}
public void afterCommit(Object resource) {
throw new RuntimeException("no tx - commit not expected");
}
public void afterRollback(Object resource) {
throw new RuntimeException("no tx - rollback not expected");
}
public void afterReceiveNoTx(Object resource) {
afterReceive.set(resource);
}
public void afterSendNoTx(Object resource) {
throw new RuntimeException("no message - after send not expected");
}
});
adapter.doPoll();
assertSame(object, afterReceive.get());
}
@Test
public void testRollback() {
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
@@ -118,7 +201,80 @@ public class PseudoTransactionalMessageSourceTests {
TransactionSynchronizationUtils.triggerAfterCompletion(TransactionSynchronization.STATUS_ROLLED_BACK);
assertSame(object, rolledBack.get());
TransactionSynchronizationManager.clearSynchronization();
TransactionSynchronizationManager.setActualTransactionActive(false);
assertNull(committed.get());
}
@Test
public void testSuccessAndFailureEvaluationWithResource() {
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
QueueChannel outputChannel = new QueueChannel();
adapter.setOutputChannel(outputChannel);
final Object object = new Bar();
final AtomicReference<Object> committed = new AtomicReference<Object>();
final AtomicReference<Object> rolledBack = new AtomicReference<Object>();
adapter.setSource(new PseudoTransactionalMessageSource<String, Object>() {
public Message<String> receive() {
return new GenericMessage<String>("foo");
}
public Object getResource() {
return object;
}
public void afterCommit(Object resource) {
committed.set(resource);
}
public void afterRollback(Object resource) {
rolledBack.set(resource);
}
public void afterReceiveNoTx(Object resource) {
}
public void afterSendNoTx(Object resource) {
}
});
TransactionSynchronizationManager.initSynchronization();
TransactionSynchronizationManager.setActualTransactionActive(true);
adapter.setOnSuccessExpression(new SpelExpressionParser().parseExpression("payload + #resource.value"));
QueueChannel success = new QueueChannel();
adapter.setOnSuccessResultChannel(success);
adapter.setOnFailureExpression(new SpelExpressionParser().parseExpression("payload + 'X' + #resource.value"));
QueueChannel failure = new QueueChannel();
adapter.setOnFailureChannel(failure);
adapter.doPoll();
TransactionSynchronizationUtils.triggerAfterCommit();
assertSame(object, committed.get());
TransactionSynchronizationUtils.triggerAfterCompletion(TransactionSynchronization.STATUS_COMMITTED);
TransactionSynchronizationManager.clearSynchronization();
TransactionSynchronizationManager.setActualTransactionActive(false);
assertNull(rolledBack.get());
Message<?> result = success.receive(10000);
assertNotNull(result);
assertEquals("foobar", result.getHeaders().get(MessageHeaders.DISPOSITION_RESULT));
committed.set(null);
TransactionSynchronizationManager.initSynchronization();
TransactionSynchronizationManager.setActualTransactionActive(true);
adapter.doPoll();
TransactionSynchronizationUtils.triggerAfterCompletion(TransactionSynchronization.STATUS_ROLLED_BACK);
assertSame(object, rolledBack.get());
TransactionSynchronizationManager.clearSynchronization();
TransactionSynchronizationManager.setActualTransactionActive(false);
assertNull(committed.get());
result = failure.receive(10000);
assertNotNull(result);
assertEquals("fooXbar", result.getHeaders().get(MessageHeaders.DISPOSITION_RESULT));
}
public class Bar {
public String getValue() {
return "bar";
}
}
}