/*
* 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.f1x;
import org.f1x.api.FixAcceptorSettings;
import org.f1x.api.FixInitiatorSettings;
import org.f1x.api.FixVersion;
import org.f1x.api.message.MessageBuilder;
import org.f1x.api.message.MessageParser;
import org.f1x.api.message.Tools;
import org.f1x.api.message.fields.*;
import org.f1x.api.session.FixSession;
import org.f1x.api.session.SessionID;
import org.f1x.api.session.SessionStatus;
import org.f1x.tools.EchoServer;
import org.f1x.v1.FixSessionInitiator;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/** Verifies that simple FIX server and client can exchange messages */
public class Test_EchoServer extends TestCommon {
private static final String INITIATOR_SENDER_ID = "INITIATOR";
private static final String ACCEPTOR_SENDER_ID = "ACCEPTOR";
/** Client and server connect, exchange 15 messages and disconnect */
@Test(timeout = 120000)
public void simpleMessageLoop() throws InterruptedException, IOException {
final EchoServer server = new EchoServer(7890, new SessionIDBean(ACCEPTOR_SENDER_ID, INITIATOR_SENDER_ID), new FixAcceptorSettings());
final EchoServerClient client = new EchoServerClient ("localhost", 7890, new SessionIDBean(INITIATOR_SENDER_ID, ACCEPTOR_SENDER_ID), 3);
final Thread acceptorThread = new Thread(server, "EchoServer");
acceptorThread.start();
final Thread initiatorThread = new Thread(client, "EchoClient");
initiatorThread.start();
if ( ! client.messageCount.await(15, TimeUnit.SECONDS))
Assert.fail("Communication failed (timed out waiting for echo)");
Assert.assertEquals(SessionStatus.ApplicationConnected, client.getSessionStatus());
normalClose(client, server);
}
/** Test verifies automatic re-connection after abrupt disconnect */
@Test(timeout = 120000)
public void loginAfterDisconnect() throws InterruptedException, IOException {
final EchoServer server = new EchoServer(7890, new SessionIDBean(ACCEPTOR_SENDER_ID, INITIATOR_SENDER_ID), new FixAcceptorSettings());
final FixSessionInitiator client = new FixSessionInitiator ("localhost", 7890, FixVersion.FIX44, new SessionIDBean(INITIATOR_SENDER_ID, ACCEPTOR_SENDER_ID), new FixInitiatorSettings());
final Thread acceptorThread = new Thread(server, "Server");
acceptorThread.start();
final Thread initiatorThread = new Thread(client, "Client");
initiatorThread.start();
if ( ! spinWaitSessionStatus(client, SessionStatus.ApplicationConnected, 15000))
Assert.fail("Timed out waiting for the first FIX session to establish");
client.disconnect("*** Reconnect Test ***");
if ( ! spinWaitSessionStatus(client, SessionStatus.Disconnected, 15000))
Assert.fail("Timed out waiting for the FIX session to go down");
if ( ! spinWaitSessionStatus(client, SessionStatus.ApplicationConnected, 35000))
Assert.fail("Timed out waiting for the FIX session to re-establish");
client.disconnect("End of test");
client.close();
server.close();
}
/** Test verifies automatic re-connection after abrupt disconnect */
@Test(timeout = 120000)
public void repeatedDisconnect() throws InterruptedException, IOException {
final EchoServer server = new EchoServer(7890, new SessionIDBean(ACCEPTOR_SENDER_ID, INITIATOR_SENDER_ID), new FixAcceptorSettings());
final FixSessionInitiator client = new FixSessionInitiator ("localhost", 7890, FixVersion.FIX44, new SessionIDBean(INITIATOR_SENDER_ID, ACCEPTOR_SENDER_ID), new FixInitiatorSettings());
final Thread acceptorThread = new Thread(server, "Server");
acceptorThread.start();
final Thread initiatorThread = new Thread(client, "Client");
initiatorThread.start();
if ( ! spinWaitSessionStatus(client, SessionStatus.ApplicationConnected, 15000))
Assert.fail("Timed out waiting for the first FIX session to establish");
client.disconnect("*** Reconnect Test ***");
if ( ! spinWaitSessionStatus(client, SessionStatus.Disconnected, 15000))
Assert.fail("Timed out waiting for the FIX session to go down");
if ( ! spinWaitSessionStatus(client, SessionStatus.ApplicationConnected, 35000))
Assert.fail("Timed out waiting for the FIX session to re-establish");
client.disconnect("End of test");
client.close();
server.close();
}
//TODO: Test reconnect after socket kill
//TODO: Test Scheduled disconnect
private boolean spinWaitSessionStatus(FixSession session, SessionStatus expectedStatus, long timeout) throws InterruptedException {
final long timeoutTime = System.currentTimeMillis() + timeout;
while (true) {
if (session.getSessionStatus() == expectedStatus)
return true;
if (System.currentTimeMillis() > timeoutTime)
return false;
Thread.yield();
}
}
private void normalClose (EchoServerClient client, EchoServer server) throws InterruptedException {
Assert.assertEquals(SessionStatus.ApplicationConnected, client.getSessionStatus());
client.close();
client.awaitDisconnect();
Assert.assertEquals(SessionStatus.Disconnected, client.getSessionStatus());
server.close();
}
private static class EchoServerClient extends FixSessionInitiator {
private final CountDownLatch messageCount;
private final MessageBuilder mb;
private final Object disconnectedSignal = new Object();
public EchoServerClient(String host, int port, SessionID sessionID, int numberOfMessagesToSend) {
super(host, port, FixVersion.FIX44, sessionID, new FixInitiatorSettings());
messageCount = new CountDownLatch(numberOfMessagesToSend);
mb = createMessageBuilder();
}
public void sendMessage () {
assert getSessionStatus() == SessionStatus.ApplicationConnected;
try {
mb.clear();
mb.setMessageType(MsgType.ORDER_SINGLE);
mb.add(FixTags.ClOrdID, 123);
mb.add(FixTags.HandlInst, HandlInst.AUTOMATED_EXECUTION_ORDER_PRIVATE);
mb.add(FixTags.OrderQty, 1);
mb.add(FixTags.OrdType, OrdType.LIMIT);
mb.add(FixTags.Price, 1.43);
mb.add(FixTags.Side, Side.BUY);
mb.add(FixTags.Symbol, "EUR/USD");
mb.add(FixTags.SecurityType, SecurityType.FOREIGN_EXCHANGE_CONTRACT);
mb.add(FixTags.TimeInForce, TimeInForce.DAY);
mb.add(76, "MARKET-FEED-SIM");
mb.add(FixTags.ExDestination, "#CANCEL-AFTER-OPEN");
mb.addUTCTimestamp(FixTags.TransactTime, System.currentTimeMillis());
send(mb);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected void onSessionStatusChanged(SessionStatus oldStatus, SessionStatus newStatus) {
super.onSessionStatusChanged(oldStatus, newStatus);
switch (newStatus) {
case ApplicationConnected:
final int cnt = (int)messageCount.getCount();
for (int i = 0; i < cnt; i++)
sendMessage();
break;
case Disconnected:
synchronized (disconnectedSignal) {
disconnectedSignal.notify();
}
break;
}
}
void awaitDisconnect() throws InterruptedException {
while(getSessionStatus() != SessionStatus.Disconnected) {
synchronized (disconnectedSignal) {
disconnectedSignal.wait();
}
}
}
@Override
protected void processInboundAppMessage(CharSequence msgType, int msgSeqNum, boolean possDup, MessageParser parser) throws IOException {
if (Tools.equals(MsgType.ORDER_SINGLE, msgType)) {
messageCount.countDown();
} else {
super.processInboundAppMessage(msgType, msgSeqNum, possDup, parser);
}
}
}
}