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

@@ -89,8 +89,6 @@ public final class MessageHeaders implements Map<String, Object>, Serializable {
public static final String CONTENT_TYPE = "content-type";
public static final String DISPOSITION_RESULT = "dispositionResult";
public static final String POSTPROCESS_RESULT = "postProcessResult";

View File

@@ -24,7 +24,6 @@ import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.SmartLifecycle;
import org.springframework.expression.Expression;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.core.MessageSource;
@@ -145,30 +144,11 @@ public class SourcePollingChannelAdapterFactoryBean implements FactoryBean<Sourc
spca.setAdviceChain(this.pollerMetadata.getAdviceChain());
spca.setTrigger(this.pollerMetadata.getTrigger());
spca.setErrorHandler(this.pollerMetadata.getErrorHandler());
Expression onSuccessExpression = this.pollerMetadata.getOnSuccessExpression();
if (onSuccessExpression != null) {
spca.setOnSuccessExpression(onSuccessExpression);
}
MessageChannel onSuccessResultChannel = this.pollerMetadata.getOnSuccessResultChannel();
if (onSuccessResultChannel != null) {
spca.setOnSuccessResultChannel(onSuccessResultChannel);
}
Expression onFailureExpression = this.pollerMetadata.getOnFailureExpression();
if (onFailureExpression != null) {
spca.setOnFailureExpression(onFailureExpression);
}
MessageChannel onFailureResultChannel = this.pollerMetadata.getOnFailureResultChannel();
if (onFailureResultChannel != null) {
spca.setOnFailureChannel(onFailureResultChannel);
}
Long resultSendTimeout = this.pollerMetadata.getSendTimeout();
if (resultSendTimeout != null) {
spca.setResultSendTimeout(resultSendTimeout);
}
spca.setBeanClassLoader(this.beanClassLoader);
spca.setAutoStartup(this.autoStartup);
spca.setBeanName(this.beanName);
spca.setBeanFactory(this.beanFactory);
spca.setTransactionSynchronizationFactory(this.pollerMetadata.getTransactionSynchronizationFactory());
spca.afterPropertiesSet();
this.adapter = spca;
this.initialized = true;

View File

@@ -16,19 +16,21 @@
package org.springframework.integration.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.SourcePollingChannelAdapterFactoryBean;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* Base parser for inbound Channel Adapters that poll a source.
*
* @author Mark Fisher
* @author Gary Russell
* @author Oleg Zhurakousky
*/
public abstract class AbstractPollingInboundChannelAdapterParser extends AbstractChannelAdapterParser {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-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.
@@ -23,6 +23,7 @@ package org.springframework.integration.config.xml;
* @author Marius Bogoevici
* @author Oleg Zhurakousky
* @author David Turanski
* @author Gary Russell
*/
public class IntegrationNamespaceHandler extends AbstractIntegrationNamespaceHandler {
@@ -72,6 +73,7 @@ public class IntegrationNamespaceHandler extends AbstractIntegrationNamespaceHan
registerBeanDefinitionParser("message-history", new MessageHistoryParser());
registerBeanDefinitionParser("control-bus", new ControlBusParser());
registerBeanDefinitionParser("wire-tap", new GlobalWireTapParser());
registerBeanDefinitionParser("transaction-synchronization-factory", new TransactionSynchronizationFactoryParser());
}
}

View File

@@ -201,7 +201,8 @@ public abstract class IntegrationNamespaceUtils {
"A 'poller' element that provides a 'ref' must have no child elements.", pollerElement);
}
targetBuilder.addPropertyReference("pollerMetadata", pollerElement.getAttribute("ref"));
} else {
}
else {
BeanDefinition beanDefinition = parserContext.getDelegate().parseCustomElement(pollerElement,
targetBuilder.getBeanDefinition());
if (beanDefinition == null) {

View File

@@ -19,22 +19,21 @@ package org.springframework.integration.config.xml;
import java.util.ArrayList;
import java.util.List;
import org.w3c.dom.Element;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.channel.MessagePublishingErrorHandler;
import org.springframework.integration.config.ExpressionFactoryBean;
import org.springframework.integration.context.IntegrationContextUtils;
import org.springframework.integration.scheduling.PollerMetadata;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.PeriodicTrigger;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;
/**
* Parser for the &lt;poller&gt; element.
@@ -93,18 +92,14 @@ public class PollerParser extends AbstractBeanDefinitionParser {
IntegrationNamespaceUtils.configureAndSetAdviceChainIfPresent(adviceChainElement, txElement,
metadataBuilder, parserContext);
Element pseudoTxElement = DomUtils.getChildElementByTagName(element, "pseudo-transactional");
if (pseudoTxElement != null && txElement != null) {
parserContext.getReaderContext().error(
"Cannot have both 'transactional' and 'pseudo-transactional' elements", element);
if (txElement != null){
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(metadataBuilder, txElement,
"synchronization-factory", "transactionSynchronizationFactory");
}
Element txSyncElement = DomUtils.getChildElementByTagName(element, "transaction-synchronization");
if (pseudoTxElement != null && txSyncElement != null) {
parserContext.getReaderContext().error(
"Cannot have both 'transaction-synchronization' and 'pseudo-transactional' elements", element);
else if (adviceChainElement != null){
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(metadataBuilder, adviceChainElement,
"synchronization-factory", "transactionSynchronizationFactory");
}
pseudoTxElement = pseudoTxElement == null ? txSyncElement : pseudoTxElement;
configureTransactionSync(pseudoTxElement, metadataBuilder, parserContext);
String errorChannel = element.getAttribute("error-channel");
if (StringUtils.hasText(errorChannel)) {
@@ -169,25 +164,4 @@ public class PollerParser extends AbstractBeanDefinitionParser {
}
targetBuilder.addPropertyReference("trigger", triggerBeanNames.get(0));
}
private void configureTransactionSync(Element element, BeanDefinitionBuilder metadataBuilder,
ParserContext parserContext) {
if (element != null) {
configureSyncExpression(element, metadataBuilder, parserContext, "on-success-expression", "onSuccessExpression");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(metadataBuilder, element, "on-success-result-channel");
configureSyncExpression(element, metadataBuilder, parserContext, "on-failure-expression", "onFailureExpression");
IntegrationNamespaceUtils.setReferenceIfAttributeDefined(metadataBuilder, element, "on-failure-result-channel");
IntegrationNamespaceUtils.setValueIfAttributeDefined(metadataBuilder, element, "send-timeout", "sendTimeout");
}
}
private void configureSyncExpression(Element element, BeanDefinitionBuilder metadataBuilder,
ParserContext parserContext, String expressionAttribute, String expressionProperty) {
String expression = element.getAttribute(expressionAttribute);
if (StringUtils.hasText(expression)) {
RootBeanDefinition expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class);
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(expression);
metadataBuilder.addPropertyValue(expressionProperty, expressionDef);
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* Copyright 2002-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.integration.config.xml;
import org.w3c.dom.Element;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.integration.config.ExpressionFactoryBean;
import org.springframework.integration.transaction.DefaultTransactionSynchronizationFactory;
import org.springframework.integration.transaction.ExpressionEvaluatingTransactionSynchronizationProcessor;
import org.springframework.util.StringUtils;
import org.springframework.util.xml.DomUtils;
/**
* Parser for transaction-synchronizatioin-factory element
*
* @author Oleg Zhurakousky
* @since 2.2
*
*/
public class TransactionSynchronizationFactoryParser extends
AbstractBeanDefinitionParser {
@Override
protected AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
BeanDefinitionBuilder syncFactoryBuilder =
BeanDefinitionBuilder.genericBeanDefinition(DefaultTransactionSynchronizationFactory.class);
Element beforeCommitElement = DomUtils.getChildElementByTagName(element, "before-commit");
Element afterCommitElement = DomUtils.getChildElementByTagName(element, "after-commit");
Element afterRollbackElement = DomUtils.getChildElementByTagName(element, "after-rollback");
if (this.elementsNotDefined(beforeCommitElement, afterCommitElement, afterRollbackElement)){
parserContext.getReaderContext().error("At least one sub-element " +
"('before-commit', 'after-commit' and/or 'after-rollback') must be defined", element);
}
BeanDefinitionBuilder expressionProcessor =
BeanDefinitionBuilder.genericBeanDefinition(ExpressionEvaluatingTransactionSynchronizationProcessor.class);
this.processSubElement(beforeCommitElement, parserContext, expressionProcessor, "beforeCommit");
this.processSubElement(afterCommitElement, parserContext, expressionProcessor, "afterCommit");
this.processSubElement(afterRollbackElement, parserContext, expressionProcessor, "afterRollback");
syncFactoryBuilder.addConstructorArgValue(expressionProcessor.getBeanDefinition());
return syncFactoryBuilder.getBeanDefinition();
}
private void processSubElement(Element element, ParserContext parserContext, BeanDefinitionBuilder expressionProcessor, String elementPrefix){
if (element != null){
String expression = element.getAttribute("expression");
String channel = element.getAttribute("channel");
if (this.attributesNotDefined(expression, channel)){
parserContext.getReaderContext().error("At least one attribute " +
"('expression' and/or 'channel') must be defined", element);
}
if (StringUtils.hasText(expression)){
RootBeanDefinition expressionDef = new RootBeanDefinition(ExpressionFactoryBean.class);
expressionDef.getConstructorArgumentValues().addGenericArgumentValue(expression);
expressionProcessor.addPropertyValue(elementPrefix + "Expression", expressionDef);
}
if (StringUtils.hasText(channel)){
expressionProcessor.addPropertyReference(elementPrefix + "Channel", channel);
}
else {
expressionProcessor.addPropertyReference(elementPrefix + "Channel", "nullChannel");
}
}
}
private boolean elementsNotDefined(Element... elements){
for (Object element : elements) {
if (element != null){
return false;
}
}
return true;
}
private boolean attributesNotDefined(String... attributes){
for (String attribute : attributes) {
if (StringUtils.hasText(attribute)){
return false;
}
}
return true;
}
}

View File

@@ -1,83 +0,0 @@
/*
* Copyright 2002-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.integration.core;
import org.springframework.transaction.support.TransactionSynchronization;
/**
* {@link MessageSource}s implementing this sub-interface can participate in
* a Spring transaction. While the underlying resource is not strictly
* transactional, the final disposition of the resource will be
* synchronized with any encompassing transaction. For example, when
* a message source is used with a transactional poller, if any upstream
* activity causes the transaction to roll back, then the {@link #afterRollback(Object)}
* method will be called, allowing the message source to reset the state of
* whatever. If the transaction commits, the {@link #afterCommit(Object)} method
* is called.<p/>
* For example, with a MailReceivingMessageSource, the email can be deleted
* on successful commit, but not deleted if the transaction rolls back.
* <p/>
* This implements the 'Best Chance 1PC' pattern where there is only a
* small (but present) window in which a transaction might commit but the
* resource is not updated to reflect that. This could result in
* duplicate messages.
* <p>All {@link MessageSource}s can have success/failure expressions evaluated either as part
* of a transaction with a &lt;transactional/&gt; poller or after success/failure when
* running in a &lt;pseudo-transactional/&gt; poller. This interface is for those
* message sources that need additional flexibility than that provided by SpEL expressions.
* @author Gary Russell
* @since 2.2
*
*/
public interface PseudoTransactionalMessageSource<T, V> extends MessageSource<T> {
/**
* Obtain the resource on which appropriate action needs
* to be taken. This resource is passed back into the other
* methods. In addition, it is made available to transaction
* synchronization SpEL expressions in the '#resource' variable.
* @return The resource.
*/
V getResource();
/**
* Invoked via {@link TransactionSynchronization} when the
* transaction commits.
* @param object The resource to be "committed"
*/
void afterCommit(Object object);
/**
* Invoked via {@link TransactionSynchronization} when the
* transaction rolls back.
* @param object
*/
void afterRollback(Object object);
/**
* Called when there is no transaction and the receive() call completed.
* @param resource
*/
void afterReceiveNoTx(V resource);
/**
* Called when there is no transaction and after the message was
* sent to the channel.
* @param resource
*/
void afterSendNoTx(V resource);
}

View File

@@ -16,24 +16,16 @@
package org.springframework.integration.endpoint;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.MessageHeaders;
import org.springframework.integration.MessagingException;
import org.springframework.integration.context.NamedComponent;
import org.springframework.integration.core.MessageSource;
import org.springframework.integration.core.MessagingTemplate;
import org.springframework.integration.core.PseudoTransactionalMessageSource;
import org.springframework.integration.history.MessageHistory;
import org.springframework.integration.history.TrackableComponent;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.util.ExpressionUtils;
import org.springframework.transaction.support.ResourceHolder;
import org.springframework.transaction.support.ResourceHolderSynchronization;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.integration.transaction.MessageSourceResourceHolder;
import org.springframework.integration.transaction.TransactionSynchronizationFactory;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
@@ -47,39 +39,26 @@ import org.springframework.util.Assert;
*/
public class SourcePollingChannelAdapter extends AbstractPollingEndpoint implements TrackableComponent {
/**
* Transaction synchronization needs a non-null resource; this constant is used for
* message sources that have no need for a resource, because the post-process
* action just needs the message.
*/
private static final Object NO_TX_RESOURCE = new Object();
private volatile MessageSource<?> source;
private volatile boolean isPseudoTxMessageSource;
private volatile MessageChannel outputChannel;
private volatile boolean shouldTrack;
private final MessagingTemplate messagingTemplate = new MessagingTemplate();
private volatile Expression onSuccessExpression;
private volatile TransactionSynchronizationFactory transactionSynchronizationFactory;
private final MessagingTemplate onSuccessMessagingTemplate = new MessagingTemplate();
private volatile Expression onFailureExpression;
private final MessagingTemplate onFailureMessagingTemplate = new MessagingTemplate();
private volatile StandardEvaluationContext evaluationContext;
public void setTransactionSynchronizationFactory(
TransactionSynchronizationFactory transactionSynchronizationFactory) {
this.transactionSynchronizationFactory = transactionSynchronizationFactory;
}
/**
* Specify the source to be polled for Messages.
*/
public void setSource(MessageSource<?> source) {
this.source = source;
this.isPseudoTxMessageSource = this.source instanceof PseudoTransactionalMessageSource;
}
/**
@@ -104,32 +83,6 @@ public class SourcePollingChannelAdapter extends AbstractPollingEndpoint impleme
this.shouldTrack = shouldTrack;
}
public void setOnSuccessExpression(Expression onSuccessExpression) {
Assert.notNull(onSuccessExpression, "onSuccessExpression cannot be null");
this.onSuccessExpression = onSuccessExpression;
}
public void setOnFailureExpression(Expression onFailureExpression) {
Assert.notNull(onFailureExpression, "onFailureExpression cannot be null");
this.onFailureExpression = onFailureExpression;
}
public void setOnSuccessResultChannel(MessageChannel onSuccessResultChannel) {
Assert.notNull(onSuccessResultChannel, "onSuccessChannel cannot be null");
this.onSuccessMessagingTemplate.setDefaultChannel(onSuccessResultChannel);
}
public void setOnFailureChannel(MessageChannel onFailureResultChannel) {
Assert.notNull(onFailureResultChannel, "onFailureChannel cannot be null");
this.onFailureMessagingTemplate.setDefaultChannel(onFailureResultChannel);
}
public void setResultSendTimeout(long sendTimeout) {
this.onSuccessMessagingTemplate.setSendTimeout(sendTimeout);
this.onFailureMessagingTemplate.setSendTimeout(sendTimeout);
}
@Override
public String getComponentType() {
return (this.source instanceof NamedComponent) ?
@@ -140,73 +93,39 @@ public class SourcePollingChannelAdapter extends AbstractPollingEndpoint impleme
protected void onInit() {
Assert.notNull(this.source, "source must not be null");
Assert.notNull(this.outputChannel, "outputChannel must not be null");
this.evaluationContext = this.createEvaluationContext();
super.onInit();
}
@SuppressWarnings("unchecked")
@Override
protected boolean doPoll() {
boolean isInTx = false;
PseudoTransactionalMessageSource<?,Object> messageSource = null;
Object resource = NO_TX_RESOURCE;
if (this.isPseudoTxMessageSource) {
messageSource = (PseudoTransactionalMessageSource<?,Object>) this.source;
}
Message<?> message;
try {
message = this.source.receive();
if (this.isPseudoTxMessageSource) {
Object actualResource = messageSource.getResource();
resource = actualResource != null ? actualResource : resource;
}
if (TransactionSynchronizationManager.isActualTransactionActive()) {
TransactionSynchronizationManager.bindResource(this, resource);
TransactionSynchronizationManager.registerSynchronization(
new PseudoTransactionalResourceSynchronization(
new PseudoTransactionalResourceHolder(message, resource), this));
isInTx = true;
}
}
finally {
if (!isInTx && this.isPseudoTxMessageSource) {
/*
* This callback is provided for 'legacy' message sources
* that used to take action after the receive and before
* the send. When in a transaction, that action is now
* taken after the commit but, when not in a transaction
* this callback provides backwards compatibility. An
* example is the mail reader that deletes from the inbox.
*/
messageSource.afterReceiveNoTx(resource);
Message<?> message;
MessageSourceResourceHolder holder = null;
if (TransactionSynchronizationManager.isActualTransactionActive()) {
holder = new MessageSourceResourceHolder(source);
TransactionSynchronizationManager.bindResource(source, holder);
if (transactionSynchronizationFactory != null){
TransactionSynchronizationManager.registerSynchronization(transactionSynchronizationFactory.create(source));
}
}
message = this.source.receive();
if (this.logger.isDebugEnabled()){
this.logger.debug("Poll resulted in Message: " + message);
}
if (message != null) {
if (holder != null) {
holder.setMessage(message);
}
if (this.shouldTrack) {
message = MessageHistory.write(message, this);
}
try {
this.messagingTemplate.send(this.outputChannel, message);
if (!isInTx) {
if (this.isPseudoTxMessageSource) {
/*
* For 'legacy' message sources that need more flexibility than simple
* expression evaluation, we invoke this callback after a
* successful send.
*/
messageSource.afterSendNoTx(resource);
}
this.onSuccess(message, resource);
}
}
catch (Exception e) {
if (!isInTx) {
this.onFailure(message, resource);
}
if (e instanceof MessagingException) {
throw (MessagingException) e;
}
@@ -221,142 +140,4 @@ public class SourcePollingChannelAdapter extends AbstractPollingEndpoint impleme
}
return false;
}
private void onSuccess(Message<?> message, Object resource) {
doPostProcess(message, resource, this.onSuccessExpression, this.onSuccessMessagingTemplate, "success");
}
private void onFailure(Message<?> message, Object resource) {
doPostProcess(message, resource, this.onFailureExpression, this.onFailureMessagingTemplate, "failure");
}
private void doPostProcess(Message<?> message, Object resource, Expression expression,
MessagingTemplate messagingTemplate, String expressionType) {
if (expression != null && message != null) {
if (logger.isDebugEnabled()) {
logger.debug("Evaluating " + expressionType + " expression: '" + expression.getExpressionString() + "' on " + message);
}
StandardEvaluationContext evaluationContextToUse = this.determineEvaluationContextToUse(resource);
Object value;
try {
value = expression.getValue(evaluationContextToUse, message);
}
catch (Exception e) {
value = e;
}
if (value != null) {
try {
messagingTemplate.send(MessageBuilder.fromMessage(message)
.setHeader(MessageHeaders.DISPOSITION_RESULT, value).build());
}
catch (Exception e) {
logger.error("Failed to send " + expressionType + " evaluation result " + message, e);
}
}
}
}
/**
* If we don't need a resource variable (not a {@link PseudoTransactionalMessageSource})
* we can use a singleton context; otherwise we need a new one each time.
* @param resource The resource
* @return The context.
*/
private StandardEvaluationContext determineEvaluationContextToUse(Object resource) {
StandardEvaluationContext evaluationContextToUse;
if (resource != NO_TX_RESOURCE) {
evaluationContextToUse = this.createEvaluationContext();
evaluationContextToUse.setVariable("resource", resource);
}
else {
if (this.evaluationContext == null) {
this.evaluationContext = this.createEvaluationContext();
}
evaluationContextToUse = this.evaluationContext;
}
return evaluationContextToUse;
}
protected StandardEvaluationContext createEvaluationContext(){
if (this.getBeanFactory() != null) {
return ExpressionUtils.createStandardEvaluationContext(new BeanFactoryResolver(this.getBeanFactory()),
this.getConversionService());
}
else {
return ExpressionUtils.createStandardEvaluationContext(this.getConversionService());
}
}
private class PseudoTransactionalResourceHolder implements ResourceHolder {
private final Message<?> message;
private final Object resource;
public PseudoTransactionalResourceHolder(Message<?> message, Object resource) {
this.message = message;
this.resource = resource;
}
protected Object getResource() {
return resource;
}
public Message<?> getMessage() {
return message;
}
public void reset() {
}
public void unbound() {
}
public boolean isVoid() {
return false;
}
}
private class PseudoTransactionalResourceSynchronization
extends ResourceHolderSynchronization<PseudoTransactionalResourceHolder, Object> {
private final PseudoTransactionalResourceHolder resourceHolder;
public PseudoTransactionalResourceSynchronization(PseudoTransactionalResourceHolder resourceHolder,
Object resourceKey) {
super(resourceHolder, resourceKey);
this.resourceHolder = resourceHolder;
}
@Override
protected boolean shouldReleaseBeforeCompletion() {
return false;
}
@Override
protected void processResourceAfterCommit(PseudoTransactionalResourceHolder resourceHolder) {
if (logger.isTraceEnabled()) {
logger.trace("'Committing' pseudo-transactional resource");
}
if (isPseudoTxMessageSource) {
((PseudoTransactionalMessageSource<?, ?>) source).afterCommit(resourceHolder.getResource());
}
onSuccess(resourceHolder.getMessage(), resourceHolder.getResource());
}
@Override
public void afterCompletion(int status) {
if (status != TransactionSynchronization.STATUS_COMMITTED) {
if (logger.isTraceEnabled()) {
logger.trace("'Rolling back' pseudo-transactional resource");
}
if (isPseudoTxMessageSource) {
((PseudoTransactionalMessageSource<?, ?>) source).afterRollback(resourceHolder.getResource());
}
onFailure(this.resourceHolder.getMessage(), this.resourceHolder.getResource());
}
super.afterCompletion(status);
}
}
}

View File

@@ -20,9 +20,10 @@ import java.util.List;
import java.util.concurrent.Executor;
import org.aopalliance.aop.Advice;
import org.springframework.expression.Expression;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.transaction.TransactionSynchronizationFactory;
import org.springframework.scheduling.Trigger;
import org.springframework.util.Assert;
import org.springframework.util.ErrorHandler;
/**
@@ -46,16 +47,20 @@ public class PollerMetadata {
private volatile Executor taskExecutor;
private volatile Expression onSuccessExpression;
private volatile MessageChannel onSuccessResultChannel;
private volatile Expression onFailureExpression;
private volatile MessageChannel onFailureResultChannel;
private volatile long sendTimeout;
private volatile TransactionSynchronizationFactory transactionSynchronizationFactory;
public void setTransactionSynchronizationFactory(
TransactionSynchronizationFactory transactionSynchronizationFactory) {
Assert.notNull(transactionSynchronizationFactory, "'transactionSynchronizationFactory' must not be null");
this.transactionSynchronizationFactory = transactionSynchronizationFactory;
}
public TransactionSynchronizationFactory getTransactionSynchronizationFactory() {
return transactionSynchronizationFactory;
}
public void setTrigger(Trigger trigger) {
this.trigger = trigger;
}
@@ -113,38 +118,6 @@ public class PollerMetadata {
return this.taskExecutor;
}
public Expression getOnSuccessExpression() {
return onSuccessExpression;
}
public void setOnSuccessExpression(Expression onSuccessExpression) {
this.onSuccessExpression = onSuccessExpression;
}
public MessageChannel getOnSuccessResultChannel() {
return onSuccessResultChannel;
}
public void setOnSuccessResultChannel(MessageChannel onSuccessResultChannel) {
this.onSuccessResultChannel = onSuccessResultChannel;
}
public Expression getOnFailureExpression() {
return onFailureExpression;
}
public void setOnFailureExpression(Expression onFailureExpression) {
this.onFailureExpression = onFailureExpression;
}
public MessageChannel getOnFailureResultChannel() {
return onFailureResultChannel;
}
public void setOnFailureResultChannel(MessageChannel onFailureResultChannel) {
this.onFailureResultChannel = onFailureResultChannel;
}
public long getSendTimeout() {
return sendTimeout;
}

View File

@@ -0,0 +1,99 @@
/*
* Copyright 2002-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.integration.transaction;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.transaction.support.ResourceHolderSynchronization;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
/**
* Default implementation of {@link TransactionSynchronizationFactory} which takes an instance of
* {@link TransactionSynchronizationProcessor} allowing you to create a {@link TransactionSynchronization}
* using {{@link #create(Object)} method.
*
* @author Gary Russell
* @author Oleg Zhurakousky
* @since 2.2
*/
public class DefaultTransactionSynchronizationFactory implements TransactionSynchronizationFactory {
private final Log logger = LogFactory.getLog(getClass());
private final TransactionSynchronizationProcessor processor;
public DefaultTransactionSynchronizationFactory(TransactionSynchronizationProcessor processor){
Assert.notNull(processor, "'processor' must not be null");
this.processor = processor;
}
public TransactionSynchronization create(Object key) {
Assert.notNull(key, "'key' must not be null");
Object resourceHolder = TransactionSynchronizationManager.getResource(key);
Assert.isInstanceOf(MessageSourceResourceHolder.class, resourceHolder);
return new DefaultTransactionalResourceSynchronization((MessageSourceResourceHolder) resourceHolder, key);
}
/**
*/
private class DefaultTransactionalResourceSynchronization
extends ResourceHolderSynchronization<MessageSourceResourceHolder, Object> {
private final MessageSourceResourceHolder messageSourceHolder;
public DefaultTransactionalResourceSynchronization(MessageSourceResourceHolder messageSourceHolder,
Object resourceKey) {
super(messageSourceHolder, resourceKey);
this.messageSourceHolder = messageSourceHolder;
}
@Override
public void beforeCommit(boolean readOnly) {
if (logger.isTraceEnabled()) {
logger.trace("'pre-Committing' transactional resource");
}
processor.processBeforeCommit(messageSourceHolder);
}
@Override
protected boolean shouldReleaseBeforeCompletion() {
return false;
}
@Override
protected void processResourceAfterCommit(MessageSourceResourceHolder resourceHolder) {
if (logger.isTraceEnabled()) {
logger.trace("'Committing' transactional resource");
}
processor.processAfterCommit(resourceHolder);
}
@Override
public void afterCompletion(int status) {
if (status != TransactionSynchronization.STATUS_COMMITTED) {
if (logger.isTraceEnabled()) {
logger.trace("'Rolling back' transactional resource");
}
processor.processAfterRollback(messageSourceHolder);
}
super.afterCompletion(status);
}
}
}

View File

@@ -0,0 +1,192 @@
/*
* Copyright 2002-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.integration.transaction;
import java.util.Map.Entry;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.integration.Message;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.context.IntegrationObjectSupport;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.util.ExpressionUtils;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.util.Assert;
/**
* This implementation of {@link TransactionSynchronizationFactory}
* allows you to configure SpEL expressions, with their execution being coordinated (synchronized) with a
* transaction - see {@link TransactionSynchronization}. Expressions for before-commit, after-commit, and after-rollback
* are supported, together with a channel for each where the evaluation result (if any) will be sent.
* For each sub-element you can specify 'expression' and/or 'channel' attributes.
* If only the 'channel' attribute is present the received Message will be sent there as part of a particular synchronization scenario.
* If only the 'expression' attribute is present and the result of an expression is a non-Null value, a Message with the
* result as the payload will be generated and sent to a default channel (NullChannel) and will appear in the logs.
* If you want the evaluation result to go to a specific channel add a 'channel' attribute. If the result of an expression is null
* or void, no Message will be generated.
*
* @author Gary Russell
* @author Oleg Zhurakousky
* @since 2.2
*
*/
public class ExpressionEvaluatingTransactionSynchronizationProcessor extends IntegrationObjectSupport implements TransactionSynchronizationProcessor {
private volatile StandardEvaluationContext evaluationContext;
private volatile Expression beforeCommitExpression;
private volatile Expression afterCommitExpression;
private volatile Expression afterRollbackExpression;
private volatile MessageChannel beforeCommitChannel;
private volatile MessageChannel afterCommitChannel;
private volatile MessageChannel afterRollbackChannel;
public void setBeforeCommitChannel(MessageChannel beforeCommitChannel) {
Assert.notNull(beforeCommitChannel, "'beforeCommitChannel' must not be null");
this.beforeCommitChannel = beforeCommitChannel;
}
public void setAfterCommitChannel(MessageChannel afterCommitChannel) {
Assert.notNull(afterCommitChannel, "'afterCommitChannel' must not be null");
this.afterCommitChannel = afterCommitChannel;
}
public void setAfterRollbackChannel(MessageChannel afterRollbackChannel) {
Assert.notNull(afterRollbackChannel, "'afterRollbackChannel' must not be null");
this.afterRollbackChannel = afterRollbackChannel;
}
public void setBeforeCommitExpression(Expression beforeCommitExpression) {
Assert.notNull(beforeCommitExpression, "'beforeCommitExpression' must not be null");
this.beforeCommitExpression = beforeCommitExpression;
}
public void setAfterCommitExpression(Expression afterCommitExpression) {
Assert.notNull(afterCommitExpression, "'afterCommitExpression' must not be null");
this.afterCommitExpression = afterCommitExpression;
}
public void setAfterRollbackExpression(Expression afterRollbackExpression) {
Assert.notNull(afterRollbackExpression, "'afterRollbackExpression' must not be null");
this.afterRollbackExpression = afterRollbackExpression;
}
public void processBeforeCommit(MessageSourceResourceHolder holder) {
this.doProcess(holder, this.beforeCommitExpression, this.beforeCommitChannel, "beforeCommit");
}
public void processAfterCommit(MessageSourceResourceHolder holder) {
this.doProcess(holder, this.afterCommitExpression, this.afterCommitChannel, "afterCommit");
}
public void processAfterRollback(MessageSourceResourceHolder holder) {
this.doProcess(holder, this.afterRollbackExpression, this.afterRollbackChannel, "afterRollback");
}
private void doProcess(MessageSourceResourceHolder holder, Expression expression, MessageChannel messageChannel, String expressionType) {
Message<?> message = holder.getMessage();
if (message != null){
if (expression != null){
if (logger.isDebugEnabled()) {
logger.debug("Evaluating " + expressionType + " expression: '" + expression.getExpressionString() + "' on " + message);
}
StandardEvaluationContext evaluationContextToUse = this.prepareEvaluationContextToUse(holder);
Object value = expression.getValue(evaluationContextToUse, message);
if (value != null) {
Message<?> spelResultMessage = null;
if (logger.isDebugEnabled()) {
logger.debug("Sending expression result message to " + messageChannel + " " +
"as part of '" + expressionType + "' transaction synchronization");
}
try {
spelResultMessage = MessageBuilder.withPayload(value).build();
this.sendMessage(messageChannel, spelResultMessage);
}
catch (Exception e) {
logger.error("Failed to send " + expressionType + " evaluation result " + spelResultMessage, e);
}
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Expression evaluation returned null");
}
}
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Sending received message to " + messageChannel + " as part of '" +
expressionType + "' transaction synchronization");
}
try {
// rollback will be initiated if any of the previous sync operations fail (e.g., beforeCommit)
// this means that this method will be called without explicit configuration thus no channel
if (messageChannel != null){
this.sendMessage(messageChannel, MessageBuilder.fromMessage(message).build());
}
} catch (Exception e) {
logger.error("Failed to send " + message, e);
}
}
}
}
private void sendMessage(MessageChannel channel, Message<?> message){
channel.send(message, 0);
}
/**
* If we don't need variables (i.e., resource is null)
* we can use a singleton context; otherwise we need a new one each time.
* @param resource The resource
* @return The context.
*/
private StandardEvaluationContext prepareEvaluationContextToUse(Object resource) {
StandardEvaluationContext evaluationContextToUse;
if (resource != null) {
evaluationContextToUse = this.createEvaluationContext();
if (resource instanceof MessageSourceResourceHolder) {
MessageSourceResourceHolder holder = (MessageSourceResourceHolder) resource;
for (Entry<String, Object> entry : holder.getAttributes().entrySet()) {
String key = entry.getKey();
Assert.state(!("messageSource".equals(key)), "'messageSource' is reserved and cannot be used as an attribute name");
evaluationContextToUse.setVariable(key, entry.getValue());
}
evaluationContextToUse.setVariable("messageSource", holder.getMessageSource());
}
}
else {
if (this.evaluationContext == null) {
this.evaluationContext = this.createEvaluationContext();
}
evaluationContextToUse = this.evaluationContext;
}
return evaluationContextToUse;
}
protected StandardEvaluationContext createEvaluationContext(){
if (this.getBeanFactory() != null) {
return ExpressionUtils.createStandardEvaluationContext(new BeanFactoryResolver(this.getBeanFactory()),
this.getConversionService());
}
else {
return ExpressionUtils.createStandardEvaluationContext(this.getConversionService());
}
}
}

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2002-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.integration.transaction;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.springframework.integration.Message;
import org.springframework.integration.core.MessageSource;
import org.springframework.transaction.support.ResourceHolder;
/**
* An implementation of the {@link ResourceHolder} which holds an instance of the current Message
* and the synchronization resource
*
* @author Gary Russell
* @author Oleg Zhurakousky
* @since 2.2
*
*/
public class MessageSourceResourceHolder implements ResourceHolder {
private final MessageSource<?> source;
private volatile Message<?> message;
private final Map<String, Object> attributes = new HashMap<String, Object>();
public MessageSourceResourceHolder(MessageSource<?> source) {
this.source = source;
}
protected MessageSource<?> getMessageSource() {
return this.source;
}
public void setMessage(Message<?> message) {
this.message = message;
}
public Message<?> getMessage() {
return message;
}
/**
* Adds attribute to this {@link ResourceHolder} instance
*
* @param key
* @param value
*/
public void addAttribute(String key, Object value){
this.attributes.put(key, value);
}
/**
* Will return an immutable Mpa of current attributes.
* If you need to add attribute use {{@link #addAttribute(String, Object)} method.
*
* @return
*/
public Map<String, Object> getAttributes() {
return Collections.unmodifiableMap(attributes);
}
public void reset() {
}
public void unbound() {
}
public boolean isVoid() {
return false;
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-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.integration.transaction;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
/**
* An implementation of {@link PlatformTransactionManager} that provides transaction-like semantics to
* {@link MessageSource}s sources that are not inherently transactional. It does <b>not<b> make such
* sources transactional; rather, together with the <transaction-synchronization> element, it provides
* the ability to synchronize operations after a flow completes, via onSucess and onFailure expressions.
*
* @author Gary Russell
* @author Oleg Zhurakousky
* @since 2.2
*
*/
public class PseudoTransactionManager extends AbstractPlatformTransactionManager {
private static final long serialVersionUID = 1L;
@Override
protected Object doGetTransaction() throws TransactionException {
return new Object();
}
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
//noop
}
@Override
protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
//noop
}
@Override
protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
//noop
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2002-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.integration.transaction;
import org.springframework.transaction.support.TransactionSynchronization;
/**
* Strategy for implementing factories that create {@link TransactionSynchronization}
*
* @author Oleg Zhurakousky
* @since 2.2
*
*/
public interface TransactionSynchronizationFactory {
TransactionSynchronization create(Object key);
}

View File

@@ -0,0 +1,31 @@
/*
* Copyright 2002-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.integration.transaction;
/**
* Strategy for implementing transaction synchronization processors
*
* @author Oleg Zhurakousky
* @author Gary Russell
* @since 2.2
*
*/
public interface TransactionSynchronizationProcessor {
public abstract void processBeforeCommit(MessageSourceResourceHolder holder);
public abstract void processAfterCommit(MessageSourceResourceHolder holder);
public abstract void processAfterRollback(MessageSourceResourceHolder holder);
}

View File

@@ -34,6 +34,66 @@
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="transaction-synchronization-factory">
<xsd:annotation>
<xsd:documentation>
Allows you to configure org.springframework.integration.transaction.DefaultTransactionSynchronizationFactory
This implementation of org.springframework.integration.transaction.TransactionSynchronizationFactory
allows you to configure SpEL expressions, with their execution being coordinated (synchronized) with a
transaction - see {TransactionSynchronization}. Expressions for before-commit, after.-commit, and after-rollback
are supported, together with a channel for each where the evaluation result (if any) will be sent.
For each sub-element you can specify 'expression' and/or 'channel' attributes.
If only the 'channel' attribute is present the received Message will be sent there as part of a particular synchronization scenario.
If only the 'expression' attribute is present and the result of an expression is a non-Null value, a Message with the
result as the payload will be generated and sent to a default channel (NullChannel) and will appear in the logs.
If you want the evaluation result to go to a specific channel add a 'channel' attribute. If the result of an expression is null
or void, no Message will be generated.
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="before-commit" minOccurs="0" maxOccurs="1">
<xsd:complexType>
<xsd:attributeGroup ref="synchronizationAttributeGroup"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="after-commit" minOccurs="0" maxOccurs="1">
<xsd:complexType>
<xsd:attributeGroup ref="synchronizationAttributeGroup"/>
</xsd:complexType>
</xsd:element>
<xsd:element name="after-rollback" minOccurs="0" maxOccurs="1">
<xsd:complexType>
<xsd:attributeGroup ref="synchronizationAttributeGroup"/>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string" use="required"/>
</xsd:complexType>
</xsd:element>
<xsd:attributeGroup name="synchronizationAttributeGroup">
<xsd:attribute name="expression" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
SpEL expression to be executed as part of Transaction synchronization
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="channel" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.MessageChannel" />
</tool:annotation>
</xsd:appinfo>
<xsd:documentation><![CDATA[
Reference to a MessageChannel where the result or an expression or received Message wil be sent.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:attributeGroup>
<xsd:element name="application-event-multicaster">
<xsd:complexType>
@@ -1494,34 +1554,6 @@
<xsd:element name="transactional" type="transactionalType" minOccurs="0" maxOccurs="1" />
<xsd:element name="advice-chain" type="adviceChainType" minOccurs="0" maxOccurs="1" />
</xsd:choice>
<xsd:choice minOccurs="0" maxOccurs="1">
<xsd:element name="pseudo-transactional" type="pseudoTransactionalType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
This element is used to provide expressions that will be executed on success or failure,
when sending a message resulting from a poll. It provides semantics similar to a transaction
but users need to be aware that it is not a true transaction because the underlying resource
is not transactional.
The element is not allowed when a <transactional/> element is present.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="transaction-synchronization" type="pseudoTransactionalType" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
This element is used with <transactional/> to provide expressions that will be executed
on success or failure, when sending a message resulting from a poll.
The execution of the expressions is synchronized
with the encompassing transaction in that the onSuccess expression will be evaluated
immediately after the commit or the onFailure expression will be evaluated immediately
after the rollback. Users need to be aware that transaction synchronization does not
make an inherently non-transactional resource transactional, it simply implements the
best-effort one phase commit pattern.
The element is only allowed when a <transactional/> or <advice-chain/> element is present.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:choice>
</xsd:sequence>
<xsd:attribute name="fixed-delay" type="xsd:string">
<xsd:annotation>
@@ -3417,6 +3449,19 @@ is provided, the return value is expected to match a channel name exactly.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="synchronization-factory" type="xsd:string" >
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to an instance of org.springframework.integration.transaction.TransactionSynchronizationFactory
which will return an instance of org.springframework.transaction.support.TransactionSynchronization via its create(..) method.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.transaction.TransactionSynchronizationFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="pseudoTransactionalType">
@@ -3442,6 +3487,20 @@ is provided, the return value is expected to match a channel name exactly.
<xsd:any namespace="##other" processContents="strict" minOccurs="0" maxOccurs="unbounded" />
</xsd:choice>
</xsd:sequence>
<xsd:attribute name="synchronization-factory" type="xsd:string" >
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to an instance of org.springframework.integration.transaction.TransactionSynchronizationFactory
which will return an instance of org.springframework.transaction.support.TransactionSynchronization via its create(..) method.
Setting of this attribute will only have an affect if a Transaction advice is present in the chain.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.transaction.TransactionSynchronizationFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:complexType name="expressionOrInnerEndpointDefinitionAware">

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.2.xsd">
<int:transaction-synchronization-factory id="syncFactoryComplete">
<int:before-commit channel="beforeCommitChannel"/>
<int:after-commit expression="'afterCommit'"/>
<int:after-rollback channel="afterRollbackChannel" expression="'afterRollback'"/>
</int:transaction-synchronization-factory>
<int:channel id="beforeCommitChannel"/>
<int:channel id="afterRollbackChannel"/>
</beans>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration-2.2.xsd">
<int:transaction-synchronization-factory id="a">
<int:before-commit channel="nullChannel"/>
</int:transaction-synchronization-factory>
<int:transaction-synchronization-factory id="b">
<int:after-commit expression="'foo'"/>
</int:transaction-synchronization-factory>
<int:transaction-synchronization-factory id="c">
<int:after-rollback expression="'f'"/>
</int:transaction-synchronization-factory>
<int:transaction-synchronization-factory id="d">
<int:before-commit channel="nullChannel" expression="''"/>
<int:after-commit channel="nullChannel" expression="''"/>
</int:transaction-synchronization-factory>
<int:transaction-synchronization-factory id="e">
<int:before-commit channel="nullChannel" expression="''"/>
<int:after-rollback channel="nullChannel" expression="''"/>
</int:transaction-synchronization-factory>
<int:transaction-synchronization-factory id="f">
<int:after-commit expression="'f'"/>
<int:after-rollback channel="nullChannel"/>
</int:transaction-synchronization-factory>
<int:transaction-synchronization-factory id="g">
<int:before-commit channel="nullChannel"/>
<int:after-commit expression="'f'"/>
<int:after-rollback channel="nullChannel" expression="''"/>
</int:transaction-synchronization-factory>
</beans>

View File

@@ -0,0 +1,72 @@
/*
* Copyright 2002-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.integration.config.xml;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpression;
import org.springframework.integration.MessageChannel;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.transaction.DefaultTransactionSynchronizationFactory;
import org.springframework.integration.transaction.ExpressionEvaluatingTransactionSynchronizationProcessor;
import org.springframework.integration.transaction.TransactionSynchronizationProcessor;
/**
* @author Oleg Zhurakousky
*/
public class TransactionSynchronizationFactoryParserTests {
@Test // nothing to assert. Validates only XSD
public void validateXsdCombinationOfOrderOfSubelements(){
new ClassPathXmlApplicationContext("TransactionSynchronizationFactoryParserTests-xsd.xml", this.getClass());
}
@Test
public void validateFullConfiguration(){
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("TransactionSynchronizationFactoryParserTests-config.xml", this.getClass());
DefaultTransactionSynchronizationFactory syncFactory =
context.getBean("syncFactoryComplete", DefaultTransactionSynchronizationFactory.class);
assertNotNull(syncFactory);
TransactionSynchronizationProcessor processor =
TestUtils.getPropertyValue(syncFactory, "processor", ExpressionEvaluatingTransactionSynchronizationProcessor.class);
assertNotNull(processor);
MessageChannel beforeCommitResultChannel = TestUtils.getPropertyValue(processor, "beforeCommitChannel", MessageChannel.class);
assertNotNull(beforeCommitResultChannel);
assertEquals(beforeCommitResultChannel, context.getBean("beforeCommitChannel"));
Object beforeCommitExpression = TestUtils.getPropertyValue(processor, "beforeCommitExpression");
assertNull(beforeCommitExpression);
MessageChannel afterCommitResultChannel = TestUtils.getPropertyValue(processor, "afterCommitChannel", MessageChannel.class);
assertNotNull(afterCommitResultChannel);
assertEquals(afterCommitResultChannel, context.getBean("nullChannel"));
Expression afterCommitExpression = TestUtils.getPropertyValue(processor, "afterCommitExpression", Expression.class);
assertNotNull(afterCommitExpression);
assertEquals("'afterCommit'", ((SpelExpression)afterCommitExpression).getExpressionString());
MessageChannel afterRollbackResultChannel = TestUtils.getPropertyValue(processor, "afterRollbackChannel", MessageChannel.class);
assertNotNull(afterRollbackResultChannel);
assertEquals(afterRollbackResultChannel, context.getBean("afterRollbackChannel"));
Expression afterRollbackExpression = TestUtils.getPropertyValue(processor, "afterRollbackExpression", Expression.class);
assertNotNull(afterRollbackExpression);
assertEquals("'afterRollback'", ((SpelExpression)afterRollbackExpression).getExpressionString());
}
}

View File

@@ -17,24 +17,28 @@ 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.core.MessageSource;
import org.springframework.integration.core.PollableChannel;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.transaction.DefaultTransactionSynchronizationFactory;
import org.springframework.integration.transaction.ExpressionEvaluatingTransactionSynchronizationProcessor;
import org.springframework.integration.transaction.MessageSourceResourceHolder;
import org.springframework.integration.transaction.PseudoTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallback;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.transaction.support.TransactionSynchronizationUtils;
import org.springframework.transaction.support.TransactionTemplate;
/**
* @author Gary Russell
* @author Oleg Zhurakousky
* @since 2.2
*
*/
@@ -43,155 +47,69 @@ public class PseudoTransactionalMessageSourceTests {
@Test
public void testCommit() {
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
ExpressionEvaluatingTransactionSynchronizationProcessor syncProcessor =
new ExpressionEvaluatingTransactionSynchronizationProcessor();
PollableChannel queueChannel = new QueueChannel();
syncProcessor.setBeforeCommitExpression(new SpelExpressionParser().parseExpression("#bix"));
syncProcessor.setBeforeCommitChannel(queueChannel);
syncProcessor.setAfterCommitChannel(queueChannel);
syncProcessor.setAfterCommitExpression(new SpelExpressionParser().parseExpression("#baz"));
DefaultTransactionSynchronizationFactory syncFactory =
new DefaultTransactionSynchronizationFactory(syncProcessor);
adapter.setTransactionSynchronizationFactory(syncFactory);
QueueChannel outputChannel = new QueueChannel();
adapter.setOutputChannel(outputChannel);
final Object object = new Object();
final AtomicReference<Object> committed = new AtomicReference<Object>();
final AtomicReference<Object> rolledBack = new AtomicReference<Object>();
adapter.setSource(new PseudoTransactionalMessageSource<String, Object>() {
adapter.setSource(new MessageSource<String>() {
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) {
GenericMessage<String> message = new GenericMessage<String>("foo");
((MessageSourceResourceHolder) TransactionSynchronizationManager.getResource(this)).addAttribute("baz", "qux");
((MessageSourceResourceHolder) TransactionSynchronizationManager.getResource(this)).addAttribute("bix", "qox");
return message;
}
});
TransactionSynchronizationManager.initSynchronization();
TransactionSynchronizationManager.setActualTransactionActive(true);
adapter.doPoll();
TransactionSynchronizationUtils.triggerBeforeCommit(false);
TransactionSynchronizationUtils.triggerAfterCommit();
assertSame(object, committed.get());
Message<?> beforeCommitMessage = queueChannel.receive(1000);
assertNotNull(beforeCommitMessage);
assertEquals("qox", beforeCommitMessage.getPayload());
Message<?> afterCommitMessage = queueChannel.receive(1000);
assertNotNull(afterCommitMessage);
assertEquals("qux", afterCommitMessage.getPayload());
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();
ExpressionEvaluatingTransactionSynchronizationProcessor syncProcessor =
new ExpressionEvaluatingTransactionSynchronizationProcessor();
PollableChannel queueChannel = new QueueChannel();
syncProcessor.setAfterRollbackChannel(queueChannel);
syncProcessor.setAfterRollbackExpression(new SpelExpressionParser().parseExpression("#baz"));
DefaultTransactionSynchronizationFactory syncFactory =
new DefaultTransactionSynchronizationFactory(syncProcessor);
adapter.setTransactionSynchronizationFactory(syncFactory);
QueueChannel outputChannel = new QueueChannel();
adapter.setOutputChannel(outputChannel);
final Object object = new Object();
final AtomicReference<Object> committed = new AtomicReference<Object>();
final AtomicReference<Object> rolledBack = new AtomicReference<Object>();
adapter.setSource(new PseudoTransactionalMessageSource<String, Object>() {
adapter.setSource(new MessageSource<String>() {
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) {
GenericMessage<String> message = new GenericMessage<String>("foo");
((MessageSourceResourceHolder) TransactionSynchronizationManager.getResource(this)).addAttribute("baz", "qux");
return message;
}
});
@@ -199,77 +117,139 @@ public class PseudoTransactionalMessageSourceTests {
TransactionSynchronizationManager.setActualTransactionActive(true);
adapter.doPoll();
TransactionSynchronizationUtils.triggerAfterCompletion(TransactionSynchronization.STATUS_ROLLED_BACK);
assertSame(object, rolledBack.get());
Message<?> rollbackMessage = queueChannel.receive(1000);
assertNotNull(rollbackMessage);
assertEquals("qux", rollbackMessage.getPayload());
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 void testCommitWithManager() {
final PollableChannel queueChannel = new QueueChannel();
TransactionTemplate transactionTemplate = new TransactionTemplate(new PseudoTransactionManager());
transactionTemplate.execute(new TransactionCallback<Object>() {
public Message<String> receive() {
return new GenericMessage<String>("foo");
}
public Object doInTransaction(TransactionStatus status) {
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
ExpressionEvaluatingTransactionSynchronizationProcessor syncProcessor =
new ExpressionEvaluatingTransactionSynchronizationProcessor();
syncProcessor.setBeforeCommitExpression(new SpelExpressionParser().parseExpression("#bix"));
syncProcessor.setBeforeCommitChannel(queueChannel);
syncProcessor.setAfterCommitChannel(queueChannel);
syncProcessor.setAfterCommitExpression(new SpelExpressionParser().parseExpression("#baz"));
public Object getResource() {
return object;
}
DefaultTransactionSynchronizationFactory syncFactory =
new DefaultTransactionSynchronizationFactory(syncProcessor);
public void afterCommit(Object resource) {
committed.set(resource);
}
adapter.setTransactionSynchronizationFactory(syncFactory);
public void afterRollback(Object resource) {
rolledBack.set(resource);
}
QueueChannel outputChannel = new QueueChannel();
adapter.setOutputChannel(outputChannel);
adapter.setSource(new MessageSource<String>() {
public void afterReceiveNoTx(Object resource) {
}
public Message<String> receive() {
GenericMessage<String> message = new GenericMessage<String>("foo");
((MessageSourceResourceHolder) TransactionSynchronizationManager.getResource(this)).addAttribute("baz", "qux");
((MessageSourceResourceHolder) TransactionSynchronizationManager.getResource(this)).addAttribute("bix", "qox");
return message;
}
});
public void afterSendNoTx(Object resource) {
adapter.doPoll();
return null;
}
});
Message<?> beforeCommitMessage = queueChannel.receive(1000);
assertNotNull(beforeCommitMessage);
assertEquals("qox", beforeCommitMessage.getPayload());
Message<?> afterCommitMessage = queueChannel.receive(1000);
assertNotNull(afterCommitMessage);
assertEquals("qux", afterCommitMessage.getPayload());
}
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);
@Test
public void testRollbackWithManager() {
final PollableChannel queueChannel = new QueueChannel();
TransactionTemplate transactionTemplate = new TransactionTemplate(new PseudoTransactionManager());
try {
transactionTemplate.execute(new TransactionCallback<Object>() {
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);
public Object doInTransaction(TransactionStatus status) {
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));
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
ExpressionEvaluatingTransactionSynchronizationProcessor syncProcessor = new ExpressionEvaluatingTransactionSynchronizationProcessor();
syncProcessor.setAfterRollbackChannel(queueChannel);
syncProcessor.setAfterRollbackExpression(new SpelExpressionParser().parseExpression("#baz"));
DefaultTransactionSynchronizationFactory syncFactory = new DefaultTransactionSynchronizationFactory(
syncProcessor);
adapter.setTransactionSynchronizationFactory(syncFactory);
QueueChannel outputChannel = new QueueChannel();
adapter.setOutputChannel(outputChannel);
adapter.setSource(new MessageSource<String>() {
public Message<String> receive() {
GenericMessage<String> message = new GenericMessage<String>("foo");
((MessageSourceResourceHolder) TransactionSynchronizationManager.getResource(this))
.addAttribute("baz", "qux");
return message;
}
});
adapter.doPoll();
throw new RuntimeException("Force rollback");
}
});
}
catch (Exception e) {
assertEquals("Force rollback", e.getMessage());
}
Message<?> rollbackMessage = queueChannel.receive(1000);
assertNotNull(rollbackMessage);
assertEquals("qux", rollbackMessage.getPayload());
}
@Test
public void testRollbackWithManagerUsingStatus() {
final PollableChannel queueChannel = new QueueChannel();
TransactionTemplate transactionTemplate = new TransactionTemplate(new PseudoTransactionManager());
transactionTemplate.execute(new TransactionCallback<Object>() {
public Object doInTransaction(TransactionStatus status) {
SourcePollingChannelAdapter adapter = new SourcePollingChannelAdapter();
ExpressionEvaluatingTransactionSynchronizationProcessor syncProcessor = new ExpressionEvaluatingTransactionSynchronizationProcessor();
syncProcessor.setAfterRollbackChannel(queueChannel);
syncProcessor.setAfterRollbackExpression(new SpelExpressionParser().parseExpression("#baz"));
DefaultTransactionSynchronizationFactory syncFactory = new DefaultTransactionSynchronizationFactory(
syncProcessor);
adapter.setTransactionSynchronizationFactory(syncFactory);
QueueChannel outputChannel = new QueueChannel();
adapter.setOutputChannel(outputChannel);
adapter.setSource(new MessageSource<String>() {
public Message<String> receive() {
GenericMessage<String> message = new GenericMessage<String>("foo");
((MessageSourceResourceHolder) TransactionSynchronizationManager.getResource(this))
.addAttribute("baz", "qux");
return message;
}
});
adapter.doPoll();
status.setRollbackOnly();
return null;
}
});
Message<?> rollbackMessage = queueChannel.receive(1000);
assertNotNull(rollbackMessage);
assertEquals("qux", rollbackMessage.getPayload());
}
public class Bar {