/*
* Copyright 2002-2017 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.ip.tcp.connection;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.logging.Log;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.integration.ip.IpHeaders;
import org.springframework.integration.ip.tcp.TcpOutboundGateway;
import org.springframework.integration.ip.tcp.TcpSendingMessageHandler;
import org.springframework.integration.ip.tcp.serializer.ByteArrayCrLfSerializer;
import org.springframework.integration.ip.util.TestingUtilities;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.integration.util.SimplePool;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessagingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.support.ErrorMessage;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Gary Russell
* @author Artem Bilan
* @since 2.2
*
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public class CachingClientConnectionFactoryTests {
@Autowired
SubscribableChannel outbound;
@Autowired
PollableChannel inbound;
@Autowired
AbstractServerConnectionFactory serverCf;
@Autowired
SubscribableChannel toGateway;
@Autowired
SubscribableChannel replies;
@Autowired
PollableChannel fromGateway;
@Autowired
@Qualifier("gateway.caching.ccf")
private CachingClientConnectionFactory gatewayCF;
@Autowired
@Qualifier("gateway.ccf")
private AbstractClientConnectionFactory clientGatewayCf;
@Autowired
@Qualifier("ccf")
private AbstractClientConnectionFactory clientAdapterCf;
@Test
public void testReuse() throws Exception {
AbstractClientConnectionFactory factory = mock(AbstractClientConnectionFactory.class);
when(factory.isRunning()).thenReturn(true);
TcpConnectionSupport mockConn1 = makeMockConnection("conn1");
TcpConnectionSupport mockConn2 = makeMockConnection("conn2");
when(factory.getConnection()).thenReturn(mockConn1).thenReturn(mockConn2);
CachingClientConnectionFactory cachingFactory = new CachingClientConnectionFactory(factory, 2);
cachingFactory.start();
TcpConnection conn1 = cachingFactory.getConnection();
// INT-3652
TcpConnectionInterceptorSupport cachedConn1 = (TcpConnectionInterceptorSupport) conn1;
Log logger = spy(TestUtils.getPropertyValue(cachedConn1, "logger", Log.class));
when(logger.isDebugEnabled()).thenReturn(true);
new DirectFieldAccessor(cachedConn1).setPropertyValue("logger", logger);
cachedConn1.onMessage(new ErrorMessage(new RuntimeException()));
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
verify(logger).debug(captor.capture());
assertThat(captor.getValue(), startsWith("Message discarded; no listener:"));
// end INT-3652
assertEquals("Cached:" + mockConn1.toString(), conn1.toString());
conn1.close();
conn1 = cachingFactory.getConnection();
assertEquals("Cached:" + mockConn1.toString(), conn1.toString());
TcpConnection conn2 = cachingFactory.getConnection();
assertEquals("Cached:" + mockConn2.toString(), conn2.toString());
conn1.close();
conn2.close();
}
@Test
public void testReuseNoLimit() throws Exception {
AbstractClientConnectionFactory factory = mock(AbstractClientConnectionFactory.class);
when(factory.isRunning()).thenReturn(true);
TcpConnectionSupport mockConn1 = makeMockConnection("conn1");
TcpConnectionSupport mockConn2 = makeMockConnection("conn2");
when(factory.getConnection()).thenReturn(mockConn1).thenReturn(mockConn2);
CachingClientConnectionFactory cachingFactory = new CachingClientConnectionFactory(factory, 0);
cachingFactory.start();
TcpConnection conn1 = cachingFactory.getConnection();
assertEquals("Cached:" + mockConn1.toString(), conn1.toString());
conn1.close();
conn1 = cachingFactory.getConnection();
assertEquals("Cached:" + mockConn1.toString(), conn1.toString());
TcpConnection conn2 = cachingFactory.getConnection();
assertEquals("Cached:" + mockConn2.toString(), conn2.toString());
conn1.close();
conn2.close();
}
@Test
public void testReuseClosed() throws Exception {
AbstractClientConnectionFactory factory = mock(AbstractClientConnectionFactory.class);
when(factory.isRunning()).thenReturn(true);
TcpConnectionSupport mockConn1 = makeMockConnection("conn1");
TcpConnectionSupport mockConn2 = makeMockConnection("conn2");
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return null;
}
}).when(mockConn1).close();
when(factory.getConnection()).thenReturn(mockConn1)
.thenReturn(mockConn2).thenReturn(mockConn1)
.thenReturn(mockConn2);
CachingClientConnectionFactory cachingFactory = new CachingClientConnectionFactory(factory, 2);
cachingFactory.start();
TcpConnection conn1 = cachingFactory.getConnection();
assertEquals("Cached:" + mockConn1.toString(), conn1.toString());
conn1.close();
conn1 = cachingFactory.getConnection();
assertEquals("Cached:" + mockConn1.toString(), conn1.toString());
TcpConnection conn2 = cachingFactory.getConnection();
assertEquals("Cached:" + mockConn2.toString(), conn2.toString());
conn1.close();
conn2.close();
when(mockConn1.isOpen()).thenReturn(false);
TcpConnection conn2a = cachingFactory.getConnection();
assertEquals("Cached:" + mockConn2.toString(), conn2a.toString());
assertSame(TestUtils.getPropertyValue(conn2, "theConnection"),
TestUtils.getPropertyValue(conn2a, "theConnection"));
conn2a.close();
}
@Test(expected = MessagingException.class)
public void testLimit() throws Exception {
AbstractClientConnectionFactory factory = mock(AbstractClientConnectionFactory.class);
when(factory.isRunning()).thenReturn(true);
TcpConnectionSupport mockConn1 = makeMockConnection("conn1");
TcpConnectionSupport mockConn2 = makeMockConnection("conn2");
when(factory.getConnection()).thenReturn(mockConn1).thenReturn(mockConn2);
CachingClientConnectionFactory cachingFactory = new CachingClientConnectionFactory(factory, 2);
cachingFactory.setConnectionWaitTimeout(10);
cachingFactory.start();
TcpConnection conn1 = cachingFactory.getConnection();
assertEquals("Cached:" + mockConn1.toString(), conn1.toString());
conn1.close();
conn1 = cachingFactory.getConnection();
assertEquals("Cached:" + mockConn1.toString(), conn1.toString());
TcpConnection conn2 = cachingFactory.getConnection();
assertEquals("Cached:" + mockConn2.toString(), conn2.toString());
cachingFactory.getConnection();
}
@Test
public void testStop() throws Exception {
AbstractClientConnectionFactory factory = mock(AbstractClientConnectionFactory.class);
when(factory.isRunning()).thenReturn(true);
TcpConnectionSupport mockConn1 = makeMockConnection("conn1");
TcpConnectionSupport mockConn2 = makeMockConnection("conn2");
int i = 3;
when(factory.getConnection()).thenReturn(mockConn1)
.thenReturn(mockConn2)
.thenReturn(makeMockConnection("conn" + (i++)));
CachingClientConnectionFactory cachingFactory = new CachingClientConnectionFactory(factory, 2);
cachingFactory.start();
TcpConnection conn1 = cachingFactory.getConnection();
assertEquals("Cached:" + mockConn1.toString(), conn1.toString());
conn1.close();
conn1 = cachingFactory.getConnection();
assertEquals("Cached:" + mockConn1.toString(), conn1.toString());
TcpConnection conn2 = cachingFactory.getConnection();
assertEquals("Cached:" + mockConn2.toString(), conn2.toString());
cachingFactory.stop();
Answer<Object> answer = new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return null;
}
};
doAnswer(answer).when(mockConn1).close();
doAnswer(answer).when(mockConn2).close();
when(factory.isRunning()).thenReturn(false);
conn1.close();
conn2.close();
verify(mockConn1).close();
verify(mockConn2).close();
when(mockConn1.isOpen()).thenReturn(false);
when(mockConn2.isOpen()).thenReturn(false);
when(factory.isRunning()).thenReturn(true);
TcpConnection conn3 = cachingFactory.getConnection();
assertNotSame(TestUtils.getPropertyValue(conn1, "theConnection"),
TestUtils.getPropertyValue(conn3, "theConnection"));
assertNotSame(TestUtils.getPropertyValue(conn2, "theConnection"),
TestUtils.getPropertyValue(conn3, "theConnection"));
}
@Test
public void testEnlargePool() throws Exception {
AbstractClientConnectionFactory factory = mock(AbstractClientConnectionFactory.class);
when(factory.isRunning()).thenReturn(true);
TcpConnectionSupport mockConn1 = makeMockConnection("conn1");
TcpConnectionSupport mockConn2 = makeMockConnection("conn2");
TcpConnectionSupport mockConn3 = makeMockConnection("conn3");
TcpConnectionSupport mockConn4 = makeMockConnection("conn4");
when(factory.getConnection()).thenReturn(mockConn1, mockConn2, mockConn3, mockConn4);
CachingClientConnectionFactory cachingFactory = new CachingClientConnectionFactory(factory, 2);
cachingFactory.start();
TcpConnection conn1 = cachingFactory.getConnection();
TcpConnection conn2 = cachingFactory.getConnection();
assertNotSame(conn1, conn2);
Semaphore semaphore = TestUtils.getPropertyValue(
TestUtils.getPropertyValue(cachingFactory, "pool"), "permits", Semaphore.class);
assertEquals(0, semaphore.availablePermits());
cachingFactory.setPoolSize(4);
TcpConnection conn3 = cachingFactory.getConnection();
TcpConnection conn4 = cachingFactory.getConnection();
assertEquals(0, semaphore.availablePermits());
conn1.close();
conn1.close();
conn2.close();
conn3.close();
conn4.close();
assertEquals(4, semaphore.availablePermits());
}
@Test
public void testReducePool() throws Exception {
AbstractClientConnectionFactory factory = mock(AbstractClientConnectionFactory.class);
when(factory.isRunning()).thenReturn(true);
TcpConnectionSupport mockConn1 = makeMockConnection("conn1", true);
TcpConnectionSupport mockConn2 = makeMockConnection("conn2", true);
TcpConnectionSupport mockConn3 = makeMockConnection("conn3", true);
TcpConnectionSupport mockConn4 = makeMockConnection("conn4", true);
when(factory.getConnection()).thenReturn(mockConn1)
.thenReturn(mockConn2).thenReturn(mockConn3)
.thenReturn(mockConn4);
CachingClientConnectionFactory cachingFactory = new CachingClientConnectionFactory(factory, 4);
cachingFactory.start();
TcpConnection conn1 = cachingFactory.getConnection();
TcpConnection conn2 = cachingFactory.getConnection();
TcpConnection conn3 = cachingFactory.getConnection();
TcpConnection conn4 = cachingFactory.getConnection();
Semaphore semaphore = TestUtils.getPropertyValue(
TestUtils.getPropertyValue(cachingFactory, "pool"), "permits", Semaphore.class);
assertEquals(0, semaphore.availablePermits());
conn1.close();
assertEquals(1, semaphore.availablePermits());
cachingFactory.setPoolSize(2);
assertEquals(0, semaphore.availablePermits());
assertEquals(3, cachingFactory.getActiveCount());
conn2.close();
assertEquals(0, semaphore.availablePermits());
assertEquals(2, cachingFactory.getActiveCount());
conn3.close();
assertEquals(1, cachingFactory.getActiveCount());
assertEquals(1, cachingFactory.getIdleCount());
conn4.close();
assertEquals(2, semaphore.availablePermits());
assertEquals(0, cachingFactory.getActiveCount());
assertEquals(2, cachingFactory.getIdleCount());
verify(mockConn1).close();
verify(mockConn2).close();
}
@Test
public void testExceptionOnSendNet() throws Exception {
TcpConnectionSupport conn1 = mockedTcpNetConnection();
TcpConnectionSupport conn2 = mockedTcpNetConnection();
CachingClientConnectionFactory cccf = createCCCFWith2Connections(conn1, conn2);
doTestCloseOnSendError(conn1, conn2, cccf);
}
@Test
public void testExceptionOnSendNio() throws Exception {
TcpConnectionSupport conn1 = mockedTcpNioConnection();
TcpConnectionSupport conn2 = mockedTcpNioConnection();
CachingClientConnectionFactory cccf = createCCCFWith2Connections(conn1, conn2);
doTestCloseOnSendError(conn1, conn2, cccf);
}
private void doTestCloseOnSendError(TcpConnection conn1, TcpConnection conn2,
CachingClientConnectionFactory cccf) throws Exception {
TcpConnection cached1 = cccf.getConnection();
try {
cached1.send(new GenericMessage<String>("foo"));
fail("Expected IOException");
}
catch (IOException e) {
assertEquals("Foo", e.getMessage());
}
// Before INT-3163 this failed with a timeout - connection not returned to pool after failure on send()
TcpConnection cached2 = cccf.getConnection();
assertTrue(cached1.getConnectionId().contains(conn1.getConnectionId()));
assertTrue(cached2.getConnectionId().contains(conn2.getConnectionId()));
}
private CachingClientConnectionFactory createCCCFWith2Connections(TcpConnectionSupport conn1, TcpConnectionSupport conn2)
throws Exception {
AbstractClientConnectionFactory factory = mock(AbstractClientConnectionFactory.class);
when(factory.isRunning()).thenReturn(true);
when(factory.getConnection()).thenReturn(conn1, conn2);
CachingClientConnectionFactory cccf = new CachingClientConnectionFactory(factory, 1);
cccf.setConnectionWaitTimeout(1);
cccf.start();
return cccf;
}
private TcpConnectionSupport mockedTcpNetConnection() throws IOException {
Socket socket = mock(Socket.class);
when(socket.isClosed()).thenReturn(true); // closed when next retrieved
OutputStream stream = mock(OutputStream.class);
doThrow(new IOException("Foo")).when(stream).write(any(byte[].class), anyInt(), anyInt());
when(socket.getOutputStream()).thenReturn(stream);
TcpNetConnection conn = new TcpNetConnection(socket, false, false, new ApplicationEventPublisher() {
@Override
public void publishEvent(ApplicationEvent event) {
}
@Override
public void publishEvent(Object event) {
}
}, "foo");
conn.setMapper(new TcpMessageMapper());
conn.setSerializer(new ByteArrayCrLfSerializer());
return conn;
}
private TcpConnectionSupport mockedTcpNioConnection() throws Exception {
SocketChannel socketChannel = mock(SocketChannel.class);
new DirectFieldAccessor(socketChannel).setPropertyValue("open", false);
doThrow(new IOException("Foo")).when(socketChannel).write(Mockito.any(ByteBuffer.class));
when(socketChannel.socket()).thenReturn(mock(Socket.class));
TcpNioConnection conn = new TcpNioConnection(socketChannel, false, false, new ApplicationEventPublisher() {
@Override
public void publishEvent(ApplicationEvent event) {
}
@Override
public void publishEvent(Object event) {
}
}, "foo");
conn.setMapper(new TcpMessageMapper());
conn.setSerializer(new ByteArrayCrLfSerializer());
return conn;
}
private TcpConnectionSupport makeMockConnection(String name) {
return makeMockConnection(name, false);
}
private TcpConnectionSupport makeMockConnection(String name, boolean closeOk) {
TcpConnectionSupport mockConn1 = mock(TcpConnectionSupport.class);
when(mockConn1.getConnectionId()).thenReturn(name);
when(mockConn1.toString()).thenReturn(name);
when(mockConn1.isOpen()).thenReturn(true);
if (!closeOk) {
doThrow(new RuntimeException("close() not expected")).when(mockConn1).close();
}
return mockConn1;
}
@Test
public void integrationTest() throws Exception {
TestingUtilities.waitListening(serverCf, null);
new DirectFieldAccessor(this.clientAdapterCf).setPropertyValue("port", this.serverCf.getPort());
this.outbound.send(new GenericMessage<>("Hello, world!"));
Message<?> m = inbound.receive(10000);
assertNotNull(m);
String connectionId = m.getHeaders().get(IpHeaders.CONNECTION_ID, String.class);
// assert we use the same connection from the pool
outbound.send(new GenericMessage<String>("Hello, world!"));
m = inbound.receive(10000);
assertNotNull(m);
assertEquals(connectionId, m.getHeaders().get(IpHeaders.CONNECTION_ID, String.class));
}
@Test
// @Repeat(1000) // INT-3722
public void gatewayIntegrationTest() throws Exception {
final List<String> connectionIds = new ArrayList<String>();
final AtomicBoolean okToRun = new AtomicBoolean(true);
Executors.newSingleThreadExecutor().execute(() -> {
while (okToRun.get()) {
Message<?> m = inbound.receive(1000);
if (m != null) {
connectionIds.add((String) m.getHeaders().get(IpHeaders.CONNECTION_ID));
replies.send(MessageBuilder.withPayload("foo:" + new String((byte[]) m.getPayload()))
.copyHeaders(m.getHeaders())
.build());
}
}
});
TestingUtilities.waitListening(serverCf, null);
new DirectFieldAccessor(this.clientGatewayCf).setPropertyValue("port", this.serverCf.getPort());
this.toGateway.send(new GenericMessage<>("Hello, world!"));
Message<?> m = fromGateway.receive(1000);
assertNotNull(m);
assertEquals("foo:" + "Hello, world!", new String((byte[]) m.getPayload()));
BlockingQueue<?> connections = TestUtils
.getPropertyValue(this.gatewayCF, "pool.available", BlockingQueue.class);
// wait until the connection is returned to the pool
int n = 0;
while (n++ < 100 && connections.size() == 0) {
Thread.sleep(100);
}
// assert we use the same connection from the pool
toGateway.send(new GenericMessage<String>("Hello, world2!"));
m = fromGateway.receive(1000);
assertNotNull(m);
assertEquals("foo:" + "Hello, world2!", new String((byte[]) m.getPayload()));
assertEquals(2, connectionIds.size());
assertEquals(connectionIds.get(0), connectionIds.get(1));
okToRun.set(false);
}
@Test
public void testCloseOnTimeoutNet() throws Exception {
TestingUtilities.waitListening(serverCf, null);
testCloseOnTimeoutGuts(new TcpNetClientConnectionFactory("localhost", serverCf.getPort()));
}
@Test
public void testCloseOnTimeoutNio() throws Exception {
TestingUtilities.waitListening(serverCf, null);
testCloseOnTimeoutGuts(new TcpNioClientConnectionFactory("localhost", serverCf.getPort()));
}
private void testCloseOnTimeoutGuts(AbstractClientConnectionFactory cf) throws Exception {
cf.setSoTimeout(100);
CachingClientConnectionFactory cccf = new CachingClientConnectionFactory(cf, 1);
cccf.start();
TcpConnection connection = cccf.getConnection();
int n = 0;
while (n++ < 100 && connection.isOpen()) {
Thread.sleep(100);
}
assertFalse(connection.isOpen());
cccf.stop();
}
@Test
public void testCachedFailover() throws Exception {
// Failover
AbstractClientConnectionFactory factory1 = mock(AbstractClientConnectionFactory.class);
AbstractClientConnectionFactory factory2 = mock(AbstractClientConnectionFactory.class);
List<AbstractClientConnectionFactory> factories = new ArrayList<AbstractClientConnectionFactory>();
factories.add(factory1);
factories.add(factory2);
TcpConnectionSupport mockConn1 = makeMockConnection();
TcpConnectionSupport mockConn2 = makeMockConnection();
when(factory1.getConnection()).thenReturn(mockConn1);
when(factory2.getConnection()).thenReturn(mockConn2);
when(factory1.isActive()).thenReturn(true);
when(factory2.isActive()).thenReturn(true);
doThrow(new IOException("fail")).when(mockConn1).send(Mockito.any(Message.class));
doAnswer(invocation -> null).when(mockConn2).send(Mockito.any(Message.class));
FailoverClientConnectionFactory failoverFactory = new FailoverClientConnectionFactory(factories);
failoverFactory.start();
// Cache
CachingClientConnectionFactory cachingFactory = new CachingClientConnectionFactory(failoverFactory, 2);
cachingFactory.start();
TcpConnection conn1 = cachingFactory.getConnection();
GenericMessage<String> message = new GenericMessage<String>("foo");
conn1 = cachingFactory.getConnection();
conn1.send(message);
Mockito.verify(mockConn2).send(message);
}
@Test
public void testCachedFailoverRealClose() throws Exception {
TcpNetServerConnectionFactory server1 = new TcpNetServerConnectionFactory(0);
server1.setBeanName("server1");
final CountDownLatch latch1 = new CountDownLatch(3);
server1.registerListener(message -> {
latch1.countDown();
return false;
});
server1.start();
TestingUtilities.waitListening(server1, 10000L);
int port1 = server1.getPort();
TcpNetServerConnectionFactory server2 = new TcpNetServerConnectionFactory(0);
server1.setBeanName("server2");
final CountDownLatch latch2 = new CountDownLatch(2);
server2.registerListener(message -> {
latch2.countDown();
return false;
});
server2.start();
TestingUtilities.waitListening(server2, 10000L);
int port2 = server2.getPort();
// Failover
AbstractClientConnectionFactory factory1 = new TcpNetClientConnectionFactory("localhost", port1);
factory1.setBeanName("client1");
factory1.registerListener(message -> false);
AbstractClientConnectionFactory factory2 = new TcpNetClientConnectionFactory("localhost", port2);
factory2.setBeanName("client2");
factory2.registerListener(message -> false);
List<AbstractClientConnectionFactory> factories = new ArrayList<AbstractClientConnectionFactory>();
factories.add(factory1);
factories.add(factory2);
FailoverClientConnectionFactory failoverFactory = new FailoverClientConnectionFactory(factories);
// Cache
CachingClientConnectionFactory cachingFactory = new CachingClientConnectionFactory(failoverFactory, 2);
cachingFactory.start();
TcpConnection conn1 = cachingFactory.getConnection();
GenericMessage<String> message = new GenericMessage<String>("foo");
conn1.send(message);
conn1.close();
TcpConnection conn2 = cachingFactory.getConnection();
assertSame(((TcpConnectionInterceptorSupport) conn1).getTheConnection(),
((TcpConnectionInterceptorSupport) conn2).getTheConnection());
conn2.send(message);
conn1 = cachingFactory.getConnection();
assertNotSame(((TcpConnectionInterceptorSupport) conn1).getTheConnection(),
((TcpConnectionInterceptorSupport) conn2).getTheConnection());
conn1.send(message);
conn1.close();
conn2.close();
assertTrue(latch1.await(10, TimeUnit.SECONDS));
server1.stop();
TestingUtilities.waitStopListening(server1, 10000L);
TestingUtilities.waitUntilFactoryHasThisNumberOfConnections(factory1, 0);
conn1 = cachingFactory.getConnection();
conn2 = cachingFactory.getConnection();
conn1.send(message);
conn2.send(message);
conn1.close();
conn2.close();
assertTrue(latch2.await(10, TimeUnit.SECONDS));
SimplePool<?> pool = TestUtils.getPropertyValue(cachingFactory, "pool", SimplePool.class);
assertEquals(2, pool.getIdleCount());
server2.stop();
}
@Test
public void testCachedFailoverRealBadHost() throws Exception {
TcpNetServerConnectionFactory server1 = new TcpNetServerConnectionFactory(0);
server1.setBeanName("server1");
final CountDownLatch latch1 = new CountDownLatch(3);
server1.registerListener(message -> {
latch1.countDown();
return false;
});
server1.start();
TestingUtilities.waitListening(server1, 10000L);
int port1 = server1.getPort();
TcpNetServerConnectionFactory server2 = new TcpNetServerConnectionFactory(0);
server1.setBeanName("server2");
final CountDownLatch latch2 = new CountDownLatch(2);
server2.registerListener(message -> {
latch2.countDown();
return false;
});
server2.start();
TestingUtilities.waitListening(server2, 10000L);
int port2 = server2.getPort();
// Failover
AbstractClientConnectionFactory factory1 = new TcpNetClientConnectionFactory("junkjunk", port1);
factory1.setBeanName("client1");
factory1.registerListener(message -> false);
AbstractClientConnectionFactory factory2 = new TcpNetClientConnectionFactory("localhost", port2);
factory2.setBeanName("client2");
factory2.registerListener(message -> false);
List<AbstractClientConnectionFactory> factories = new ArrayList<AbstractClientConnectionFactory>();
factories.add(factory1);
factories.add(factory2);
FailoverClientConnectionFactory failoverFactory = new FailoverClientConnectionFactory(factories);
// Cache
CachingClientConnectionFactory cachingFactory = new CachingClientConnectionFactory(failoverFactory, 2);
cachingFactory.start();
TcpConnection conn1 = cachingFactory.getConnection();
GenericMessage<String> message = new GenericMessage<String>("foo");
conn1.send(message);
conn1.close();
TcpConnection conn2 = cachingFactory.getConnection();
assertSame(((TcpConnectionInterceptorSupport) conn1).getTheConnection(),
((TcpConnectionInterceptorSupport) conn2).getTheConnection());
conn2.send(message);
conn1 = cachingFactory.getConnection();
assertNotSame(((TcpConnectionInterceptorSupport) conn1).getTheConnection(),
((TcpConnectionInterceptorSupport) conn2).getTheConnection());
conn1.send(message);
conn1.close();
conn2.close();
assertTrue(latch2.await(10, TimeUnit.SECONDS));
assertEquals(3, latch1.getCount());
server1.stop();
server2.stop();
}
@Test //INT-3650
public void testRealConnection() throws Exception {
TcpNetServerConnectionFactory in = new TcpNetServerConnectionFactory(0);
final CountDownLatch latch1 = new CountDownLatch(2);
final CountDownLatch latch2 = new CountDownLatch(102);
final List<String> connectionIds = new ArrayList<String>();
in.registerListener(message -> {
connectionIds.add((String) message.getHeaders().get(IpHeaders.CONNECTION_ID));
latch1.countDown();
latch2.countDown();
return false;
});
in.start();
TestingUtilities.waitListening(in, null);
int port = in.getPort();
TcpNetClientConnectionFactory out = new TcpNetClientConnectionFactory("localhost", port);
CachingClientConnectionFactory cache = new CachingClientConnectionFactory(out, 1);
cache.setSingleUse(false);
cache.setConnectionWaitTimeout(100);
cache.start();
TcpConnectionSupport connection1 = cache.getConnection();
connection1.send(new GenericMessage<String>("foo"));
connection1.close();
TcpConnectionSupport connection2 = cache.getConnection();
connection2.send(new GenericMessage<String>("foo"));
connection2.close();
assertTrue(latch1.await(10, TimeUnit.SECONDS));
assertSame(connectionIds.get(0), connectionIds.get(1));
for (int i = 0; i < 100; i++) {
TcpConnectionSupport connection = cache.getConnection();
connection.send(new GenericMessage<String>("foo"));
connection.close();
}
assertTrue(latch2.await(10, TimeUnit.SECONDS));
assertSame(connectionIds.get(0), connectionIds.get(101));
in.stop();
cache.stop();
}
@SuppressWarnings("unchecked")
@Test //INT-3722
public void testGatewayRelease() throws Exception {
TcpNetServerConnectionFactory in = new TcpNetServerConnectionFactory(0);
in.setApplicationEventPublisher(mock(ApplicationEventPublisher.class));
final TcpSendingMessageHandler handler = new TcpSendingMessageHandler();
handler.setConnectionFactory(in);
final AtomicInteger count = new AtomicInteger(2);
in.registerListener(message -> {
if (!(message instanceof ErrorMessage)) {
if (count.decrementAndGet() < 1) {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
handler.handleMessage(message);
}
return false;
});
handler.setBeanFactory(mock(BeanFactory.class));
handler.afterPropertiesSet();
handler.start();
TestingUtilities.waitListening(in, null);
int port = in.getPort();
TcpNetClientConnectionFactory out = new TcpNetClientConnectionFactory("localhost", port);
out.setApplicationEventPublisher(mock(ApplicationEventPublisher.class));
CachingClientConnectionFactory cache = new CachingClientConnectionFactory(out, 2);
final TcpOutboundGateway gate = new TcpOutboundGateway();
gate.setConnectionFactory(cache);
QueueChannel outputChannel = new QueueChannel();
gate.setOutputChannel(outputChannel);
gate.setBeanFactory(mock(BeanFactory.class));
gate.afterPropertiesSet();
Log logger = spy(TestUtils.getPropertyValue(gate, "logger", Log.class));
new DirectFieldAccessor(gate).setPropertyValue("logger", logger);
when(logger.isDebugEnabled()).thenReturn(true);
doAnswer(new Answer<Void>() {
private final CountDownLatch latch = new CountDownLatch(2);
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
invocation.callRealMethod();
String log = invocation.getArgument(0);
if (log.startsWith("Response")) {
Executors.newSingleThreadScheduledExecutor()
.execute(() -> gate.handleMessage(new GenericMessage<>("bar")));
// hold up the first thread until the second has added its pending reply
latch.await(10, TimeUnit.SECONDS);
}
else if (log.startsWith("Added")) {
latch.countDown();
}
return null;
}
}).when(logger).debug(anyString());
gate.start();
gate.handleMessage(new GenericMessage<String>("foo"));
Message<byte[]> result = (Message<byte[]>) outputChannel.receive(10000);
assertNotNull(result);
assertEquals("foo", new String(result.getPayload()));
result = (Message<byte[]>) outputChannel.receive(10000);
assertNotNull(result);
assertEquals("bar", new String(result.getPayload()));
handler.stop();
gate.stop();
verify(logger, never()).error(anyString());
}
@Test // INT-3728
public void testEarlyReceive() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final AbstractClientConnectionFactory factory = new TcpNetClientConnectionFactory("", 0) {
@Override
protected Socket createSocket(String host, int port) throws IOException {
Socket mock = mock(Socket.class);
when(mock.getInputStream()).thenReturn(new ByteArrayInputStream("foo\r\n".getBytes()));
return mock;
}
@Override
public boolean isActive() {
return true;
}
};
factory.setApplicationEventPublisher(mock(ApplicationEventPublisher.class));
final CachingClientConnectionFactory cachingFactory = new CachingClientConnectionFactory(factory, 1);
final AtomicReference<Message<?>> received = new AtomicReference<Message<?>>();
cachingFactory.registerListener(message -> {
if (!(message instanceof ErrorMessage)) {
received.set(message);
latch.countDown();
}
return false;
});
cachingFactory.start();
cachingFactory.getConnection();
assertTrue(latch.await(10, TimeUnit.SECONDS));
assertNotNull(received.get());
assertNotNull(received.get().getHeaders().get(IpHeaders.ACTUAL_CONNECTION_ID));
cachingFactory.stop();
}
private TcpConnectionSupport makeMockConnection() {
TcpConnectionSupport connection = mock(TcpConnectionSupport.class);
when(connection.isOpen()).thenReturn(true);
return connection;
}
}