INT-3266 (S)FTP Close Dirty Cached Sessions

If an exception occurs on a session it should be physically closed
and not reused because its state is indeterminate and the next
operation might fail.

Note: The booleans within CachedSession do not need to be volatile
because it is a short-lived object only used by the current thread.

Also fixes the assertion message in RFT.get().

JIRA: https://jira.springsource.org/browse/INT-3266

INT-3266 Polishing - PR Comments

Remove need for SuppressWarnings.
Fix 2 test cases where the exception has an additional cause.

Fix invalid `GenericMessage` import
Fix `e.getCause()` in `FtpServerOutboundTests` & `SftpServerOutboundTests` tests
This commit is contained in:
Gary Russell
2014-01-21 11:58:11 +02:00
committed by Artem Bilan
parent 6df2e5b026
commit da4ec8f659
3 changed files with 64 additions and 4 deletions

View File

@@ -36,6 +36,7 @@ import org.springframework.integration.MessageDeliveryException;
import org.springframework.integration.MessagingException;
import org.springframework.integration.file.DefaultFileNameGenerator;
import org.springframework.integration.file.FileNameGenerator;
import org.springframework.integration.file.remote.session.CachingSessionFactory;
import org.springframework.integration.file.remote.session.Session;
import org.springframework.integration.file.remote.session.SessionFactory;
import org.springframework.integration.handler.ExpressionEvaluatingMessageProcessor;
@@ -272,7 +273,7 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
@Override
public boolean get(final Message<?> message, final InputStreamCallback callback) {
Assert.notNull(this.fileNameProcessor, "'fileNameProcessor' needed to use get");
Assert.notNull(this.fileNameProcessor, "A 'fileNameExpression' is needed to use get");
return this.execute(new SessionCallback<F, Boolean>() {
@Override
@@ -294,7 +295,13 @@ public class RemoteFileTemplate<F> implements RemoteFileOperations<F>, Initializ
Assert.notNull(session, "failed to acquire a Session");
return callback.doInSession(session);
}
catch (IOException e) {
catch (Exception e) {
if (session instanceof CachingSessionFactory<?>.CachedSession) {
((CachingSessionFactory<?>.CachedSession) session).dirty();
}
if (e instanceof MessagingException) {
throw (MessagingException) e;
}
throw new MessagingException("Failed to execute on session", e);
}
finally {

View File

@@ -147,11 +147,13 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
this.pool.removeAllIdleItems();
}
private class CachedSession implements Session<F> {
public class CachedSession implements Session<F> {
private final Session<F> targetSession;
private volatile boolean released;
private boolean released;
private boolean dirty;
/**
* The epoch in which this session was created.
@@ -180,6 +182,9 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
}
this.targetSession.close();
}
else if (this.dirty) {
this.targetSession.close();
}
pool.releaseItem(targetSession);
released = true;
}
@@ -240,6 +245,10 @@ public class CachingSessionFactory<F> implements SessionFactory<F>, DisposableBe
return this.targetSession.finalizeRaw();
}
public void dirty() {
this.dirty = true;
}
}
}

View File

@@ -15,16 +15,29 @@
*/
package org.springframework.integration.file.remote.session;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.file.remote.InputStreamCallback;
import org.springframework.integration.file.remote.RemoteFileTemplate;
import org.springframework.integration.message.GenericMessage;
import org.springframework.integration.test.util.TestUtils;
/**
@@ -63,6 +76,37 @@ public class CachingSessionFactoryTests {
assertFalse(sess1.isOpen());
}
@Test
public void testDirtySession() throws Exception {
@SuppressWarnings("unchecked")
SessionFactory<Object> factory = mock(SessionFactory.class);
@SuppressWarnings("unchecked")
Session<Object> session = mock(Session.class);
when(factory.getSession()).thenReturn(session);
when(session.readRaw("foo")).thenReturn(new ByteArrayInputStream("".getBytes()));
when(session.finalizeRaw()).thenReturn(true);
CachingSessionFactory<Object> ccf = new CachingSessionFactory<Object>(factory);
RemoteFileTemplate<Object> template = new RemoteFileTemplate<Object>(ccf);
template.setFileNameExpression(new LiteralExpression("foo"));
template.setBeanFactory(mock(BeanFactory.class));
template.afterPropertiesSet();
try {
template.get(new GenericMessage<String>("foo"), new InputStreamCallback() {
@Override
public void doWithInputStream(InputStream stream) throws IOException {
throw new RuntimeException("bar");
}
});
fail("Expected exception");
}
catch (Exception e) {
assertThat(e.getCause(), instanceOf(RuntimeException.class));
assertThat(e.getCause().getMessage(), equalTo("bar"));
}
verify(session).close();
}
private class TestSessionFactory implements SessionFactory<String> {
private int n;