/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.camel.itest.ftp;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.net.SocketFactory;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.impl.JndiRegistry;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.apache.commons.net.ftp.FTPClient;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockftpserver.fake.FakeFtpServer;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class FtpInitialConnectTimeoutTest extends CamelTestSupport {
private static final int CONNECT_TIMEOUT = 11223;
/**
* Create the answer for the socket factory that causes a SocketTimeoutException to occur in connect.
*/
private static class SocketAnswer implements Answer<Socket> {
@Override
public Socket answer(InvocationOnMock invocation) throws Throwable {
final Socket socket = Mockito.spy(new Socket());
final AtomicBoolean timeout = new AtomicBoolean();
try {
doAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock invocation) throws Throwable {
final InputStream stream = (InputStream) invocation.callRealMethod();
InputStream inputStream = new InputStream() {
@Override
public int read() throws IOException {
if (timeout.get()) {
// emulate a timeout occurring in _getReply()
throw new SocketTimeoutException();
}
return stream.read();
}
};
return inputStream;
}
}).when(socket).getInputStream();
} catch (IOException ignored) {
}
try {
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
if ((Integer) invocation.getArguments()[0] == CONNECT_TIMEOUT) {
// setting of connect timeout
timeout.set(true);
} else {
// non-connect timeout
timeout.set(false);
}
return invocation.callRealMethod();
}
}).when(socket).setSoTimeout(anyInt());
} catch (SocketException e) {
throw new RuntimeException(e);
}
return socket;
}
}
private FakeFtpServer fakeFtpServer;
@Override
@Before
public void setUp() throws Exception {
fakeFtpServer = new FakeFtpServer();
fakeFtpServer.setServerControlPort(0);
fakeFtpServer.start();
super.setUp();
}
@Override
@After
public void tearDown() throws Exception {
super.tearDown();
if (fakeFtpServer != null) {
fakeFtpServer.stop();
}
}
private FTPClient mockedClient() throws IOException {
FTPClient client = new FTPClient();
client.setSocketFactory(createSocketFactory());
return client;
}
private SocketFactory createSocketFactory() throws IOException {
SocketFactory socketFactory = mock(SocketFactory.class);
when(socketFactory.createSocket()).thenAnswer(new SocketAnswer());
return socketFactory;
}
@Override
protected JndiRegistry createRegistry() throws Exception {
JndiRegistry registry = super.createRegistry();
registry.bind("mocked", mockedClient());
return registry;
}
@Test
public void testReConnect() throws Exception {
// we should fail, but we are testing that we are not in a deadlock which could potentially happen
getMockEndpoint("mock:done").expectedMessageCount(0);
getMockEndpoint("mock:dead").expectedMessageCount(1);
sendBody("direct:start", "test");
assertMockEndpointsSatisfied();
}
@Override
protected RouteBuilder createRouteBuilder() throws Exception {
return new RouteBuilder() {
@Override
public void configure() throws Exception {
errorHandler(deadLetterChannel("mock:dead"));
// using soTimeout=0 could potentially cause the ftp producer to dead-lock doing endless reconnection attempts
// this is a test to ensure we have fixed that
from("direct:start")
.to("ftp://localhost:" + fakeFtpServer.getServerControlPort()
+ "?ftpClient=#mocked"
+ "&soTimeout=0&"
+ "connectTimeout=" + CONNECT_TIMEOUT)
.to("mock:done");
}
};
}
}