INT-1849/INT-2147 Add Disposition to File Adapter

* Add *disposition-expression* to the inbound file adapter. This allows for operations such as *payload.delete()*, *payload.renameTo()* after the file is processed.
* The result of executing the expression (if any) is sent to the disposition-result-channel.
This commit is contained in:
Gary Russell
2012-06-14 17:47:46 -04:00
committed by Gunnar Hillert
parent 703122a6fb
commit 066eb91100
18 changed files with 440 additions and 86 deletions

View File

@@ -38,27 +38,40 @@ import org.springframework.transaction.support.TransactionSynchronization;
* @since 2.2
*
*/
public interface PseudoTransactionalMessageSource<T> extends MessageSource<T> {
public interface PseudoTransactionalMessageSource<T, V> extends MessageSource<T> {
/**
* Obtain the resource on which appropriate action needs
* to be taken.
* @return The resource.
*/
Object getResource();
V getResource();
/**
* Invoked via {@link TransactionSynchronization} when the
* transaction commits.
* @param resource The resource to be "committed"
*/
void afterCommit(Object resource);
void afterCommit(V resource);
/**
* Invoked via {@link TransactionSynchronization} when the
* transaction rolls back.
* @param resource
*/
void afterRollback(Object resource);
void afterRollback(V resource);
/**
* 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

@@ -99,13 +99,14 @@ public class SourcePollingChannelAdapter extends AbstractPollingEndpoint impleme
super.onInit();
}
@SuppressWarnings("unchecked")
@Override
protected boolean doPoll() {
boolean isInTx = false;
PseudoTransactionalMessageSource<?> messageSource = null;
PseudoTransactionalMessageSource<?,Object> messageSource = null;
Object resource = null;
if (this.isPseudoTxMessageSource) {
messageSource = (PseudoTransactionalMessageSource<?>) this.source;
messageSource = (PseudoTransactionalMessageSource<?,Object>) this.source;
resource = messageSource.getResource();
Assert.state(resource != null, "Pseudo Transactional Message Source returned null resource");
if (this.synchronizedTx && TransactionSynchronizationManager.isActualTransactionActive()) {
@@ -122,17 +123,7 @@ public class SourcePollingChannelAdapter extends AbstractPollingEndpoint impleme
}
finally {
if (this.isPseudoTxMessageSource && !isInTx) {
/*
* If the message source implements PseudoTransactionalMessageSource and
* we're running from a transactional poller, the message source's afterCommit
* method will be called by the transaction interceptor, using the transaction
* synchronization callback, after the transaction is committed.
*
* If we are not running in a transaction, we invoke it manually, so the message
* source can take the appropriate action, immediately after the receive;
* this was the behavior before pseudo transaction support was added.
*/
messageSource.afterCommit(resource);
messageSource.afterReceiveNoTx(resource);
}
}
if (this.logger.isDebugEnabled()){
@@ -143,6 +134,9 @@ public class SourcePollingChannelAdapter extends AbstractPollingEndpoint impleme
message = MessageHistory.write(message, this);
}
this.messagingTemplate.send(this.outputChannel, message);
if (this.isPseudoTxMessageSource && !isInTx) {
messageSource.afterSendNoTx(resource);
}
return true;
}
if (this.logger.isDebugEnabled()){
@@ -191,21 +185,23 @@ public class SourcePollingChannelAdapter extends AbstractPollingEndpoint impleme
return false;
}
@SuppressWarnings("unchecked")
@Override
protected void processResourceAfterCommit(PseudoTransactionalResourceHolder resourceHolder) {
if (logger.isTraceEnabled()) {
logger.trace("'Committing' pseudo-transactional resource");
}
((PseudoTransactionalMessageSource<?>) source).afterCommit(resourceHolder.getResource());
((PseudoTransactionalMessageSource<?,Object>) source).afterCommit(resourceHolder.getResource());
}
@SuppressWarnings("unchecked")
@Override
public void afterCompletion(int status) {
if (status != TransactionSynchronization.STATUS_COMMITTED) {
if (logger.isTraceEnabled()) {
logger.trace("'Rolling back' pseudo-transactional resource");
}
((PseudoTransactionalMessageSource<?>) source).afterRollback(this.resourceHolder.getResource());
((PseudoTransactionalMessageSource<?,Object>) source).afterRollback(this.resourceHolder.getResource());
}
super.afterCompletion(status);
}

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.util;
import org.springframework.context.expression.MapAccessor;
import org.springframework.core.convert.ConversionService;
import org.springframework.expression.BeanResolver;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.expression.spel.support.StandardTypeConverter;
/**
* Utility class with static methods for helping with establishing environments for
* SpEL expressions.
*
* @author Gary Russell
* @since 2.2
*
*/
public class ExpressionUtils {
/**
* Create a {@link StandardEvaluationContext} with a {@link MapAccessor} in its
* property accessor property.
* @return the evaluation context.
*/
public static StandardEvaluationContext createStandardEvaluationContext() {
return createStandardEvaluationContext(null, null);
}
/**
* Create a {@link StandardEvaluationContext} with a {@link MapAccessor} in its
* property accessor property and the supplied {@link BeanResolver} in its
* beanResolver property.
* @param beanResolver the bean factory.
* @return the evaluation context.
*/
public static StandardEvaluationContext createStandardEvaluationContext(BeanResolver beanResolver) {
return createStandardEvaluationContext(beanResolver, null);
}
/**
* Create a {@link StandardEvaluationContext} with a {@link MapAccessor} in its
* property accessor property and the supplied {@link ConversionService} in its
* conversionService property.
* @param conversionService the conversion service.
* @return the evaluation context.
*/
public static StandardEvaluationContext createStandardEvaluationContext(ConversionService conversionService) {
return createStandardEvaluationContext(null, conversionService);
}
/**
* Create a {@link StandardEvaluationContext} with a {@link MapAccessor} in its
* property accessor property, the supplied {@link BeanResolver} in its
* beanResolver property, and the supplied {@link ConversionService} in its
* conversionService property.
* @param beanResolver the bean factory.
* @param conversionService the conversion service.
* @return the evaluation context.
*/
public static StandardEvaluationContext createStandardEvaluationContext(BeanResolver beanResolver,
ConversionService conversionService) {
StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
evaluationContext.addPropertyAccessor(new MapAccessor());
if (beanResolver != null) {
evaluationContext.setBeanResolver(beanResolver);
}
if (conversionService != null) {
evaluationContext.setTypeConverter(new StandardTypeConverter(conversionService));
}
return evaluationContext;
}
}

View File

@@ -44,7 +44,7 @@ public class PseudoTransactionalMessageSourceTests {
final Object object = new Object();
final AtomicReference<Object> committed = new AtomicReference<Object>();
final AtomicReference<Object> rolledBack = new AtomicReference<Object>();
adapter.setSource(new PseudoTransactionalMessageSource<String>() {
adapter.setSource(new PseudoTransactionalMessageSource<String, Object>() {
public Message<String> receive() {
return new GenericMessage<String>("foo");
@@ -61,6 +61,12 @@ public class PseudoTransactionalMessageSourceTests {
public void afterRollback(Object resource) {
rolledBack.set(resource);
}
public void afterReceiveNoTx(Object resource) {
}
public void afterSendNoTx(Object resource) {
}
});
TransactionSynchronizationManager.initSynchronization();
@@ -81,7 +87,7 @@ public class PseudoTransactionalMessageSourceTests {
final Object object = new Object();
final AtomicReference<Object> committed = new AtomicReference<Object>();
final AtomicReference<Object> rolledBack = new AtomicReference<Object>();
adapter.setSource(new PseudoTransactionalMessageSource<String>() {
adapter.setSource(new PseudoTransactionalMessageSource<String, Object>() {
public Message<String> receive() {
return new GenericMessage<String>("foo");
@@ -98,6 +104,12 @@ public class PseudoTransactionalMessageSourceTests {
public void afterRollback(Object resource) {
rolledBack.set(resource);
}
public void afterReceiveNoTx(Object resource) {
}
public void afterSendNoTx(Object resource) {
}
});
TransactionSynchronizationManager.initSynchronization();