/**
* Blitz Trading
*/
package executionserver.controller;
import BE.BEConnectResponse;
import BE.BEOrderList;
import BE.BEOrderUpdate;
import executionserver.domain.ExecutionOrder;
import executionserver.domain.OrderStatus;
import executionserver.domain.RequestTypes;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import org.apache.mina.common.IoSession;
import org.slf4j.LoggerFactory;
/**
* ExecutionServer connection object, controls the server operations with FIX
* initiator (client).
*
* @author Sylvio Azevedo <sylvio.azevedo@blitz-trading.com>
*/
public class ExecutionController {
// properties
private String clientName;
private IoSession session;
private final DatabaseController database;
private final Object ordersToProcessMutex = new Object();
private List<ExecutionOrder> ordersToProcess;
private boolean running = true;
private QueueWatcher watcher;
private Thread watcherThread;
// logger
private final org.slf4j.Logger logger = LoggerFactory.getLogger(getClass());
/**
* Constructor storing the client name and the connection session.
*
* @param clientName Connection client name
* @param session Connection session reference.
*
* @throws UnknownHostException
*/
public ExecutionController(String clientName, IoSession session) throws UnknownHostException {
this.clientName = clientName;
this.session = session;
// instance temporary queue for orders to process.
ordersToProcess = new ArrayList<ExecutionOrder>();
// start a connection to mongo database
database = new DatabaseController();
database.connect(ExecutionServerController.settings);
}
public void startQueueWatcher() {
// start queue watcher
watcher = new QueueWatcher();
watcherThread = new Thread(watcher);
watcherThread.start();
}
public void stopQueueWatcher() {
running = false;
synchronized (ordersToProcessMutex) {
ordersToProcessMutex.notify();
}
}
/**
* Add a new order in the processing queue
*
* @param order Order about be enqueued.
*
* @throws InterruptedException
*/
public void addOrder(ExecutionOrder order) throws InterruptedException {
// put order in the temporary queue
ordersToProcess.add(order);
// Send message "received" to client.
BEOrderUpdate.OrderUpdate response = BEOrderUpdate.OrderUpdate.newBuilder()
.setQtyRemaining((long) order.getQty())
.setStatus(OrderStatus.RECEIVED)
.setOrderId(order.getId())
.build();
session.write(response);
synchronized (ordersToProcessMutex) {
ordersToProcessMutex.notify();
}
}
/**
* Order rejection method.
*
* @param order Rejected order reference.
* @param message Rejection message.
*/
private void reject(ExecutionOrder order, String message) {
logger.warn("Order [" + order.getId() + "] has been rejected with message: " + message);
BEOrderUpdate.OrderUpdate response = BEOrderUpdate.OrderUpdate.newBuilder()
.setQtyRemaining((long) order.getQty())
.setStatus(OrderStatus.REJECTED)
.setOrderId(order.getId())
.setRejectReason(message)
.build();
session.write(response);
}
/**
* Send all available connections (routings) to the client part.
*/
public void sendAvailableConns() {
// prepare connect response object.
BEConnectResponse.ConnectResponse.Builder response = BEConnectResponse.ConnectResponse.newBuilder();
for (String conn : ExecutionServerController.connections.keySet()) {
response.addRouteName(conn);
}
session.write(response.build());
}
/**
* Send a list of orders from a particular client.
*/
public void sendOrderList() {
List<ExecutionOrder> orderList = database.findOrdersByOwner(clientName);
if (orderList.isEmpty()) {
return;
}
BEOrderList.DataOrderList.Builder response = BEOrderList.DataOrderList.newBuilder();
for (ExecutionOrder order : orderList) {
BEOrderList.DataOrder.Builder newOrder = BEOrderList.DataOrder.newBuilder();
newOrder.setOrderId(order.getClientId());
newOrder.setClientId(order.getClientId());
newOrder.setSymbol(order.getSecurity());
newOrder.setSide(order.getSide());
newOrder.setType(order.getOrderType());
newOrder.setExchange(order.getExchange());
newOrder.setAccountId(order.getAccount());
newOrder.setValidity(order.getValidity());
newOrder.setStatus(order.getOrderStatus());
newOrder.setPrice(order.getPrice());
newOrder.setQuantity((long) order.getQty());
newOrder.setMinqty((long) order.getMinQty());
newOrder.setOpenqty((long) order.getOpenQty());
newOrder.setEntrytime(0);
newOrder.setStoppx(order.getStopPrice());
newOrder.setLastPrice(order.getLastPrice());
newOrder.setLastShares(order.getLastShares());
newOrder.setRoute(order.getRoute());
newOrder.setBroker(order.getBroker());
if (newOrder.hasRejectReason()) {
newOrder.setRejectReason(order.getRejectReason());
}
response.addOrder(newOrder);
}
try {
// send orders to client.
session.write(response.build());
} catch (Exception e) {
logger.error("Error sending client list order: " + e.getMessage());
}
}
private class QueueWatcher implements Runnable {
@Override
public void run() {
while (running) {
synchronized (ordersToProcessMutex) {
try {
ordersToProcessMutex.wait(50);
}
catch (InterruptedException ex) {
logger.warn("Queue watcher has been interrupted. All order will be stucked in the queue: " + ex.getMessage());
}
}
while (!ordersToProcess.isEmpty()) {
// remove the head of the queue
ExecutionOrder order = ordersToProcess.remove(0);
switch (order.getReqType()) {
case RequestTypes.REQUEST_NEW_ORDER:
// Check if the appointed fix connection exists.
if (ExecutionServerController.connections.get(order.getRoute()) == null) {
// Send reject message.
reject(order, "[ExecutionServer] Invalid routing name: " + order.getRoute());
return;
} {
// Check if order already exists.
if (database.exists(order)) {
// Send reject message.
reject(order, "[ExecutionServer] Duplicated order id.");
continue;
}
// Send message "received" to client.
BEOrderUpdate.OrderUpdate response = BEOrderUpdate.OrderUpdate.newBuilder()
.setQtyRemaining((long) order.getQty())
.setStatus(OrderStatus.ACCEPTED)
.setOrderId(order.getId())
.build();
session.write(response);
// Change order status.
order.setOrderStatus(OrderStatus.ACCEPTED);
database.insert(order);
}
break;
case RequestTypes.REQUEST_REPLACE: {
// Retrieve order from database.
ExecutionOrder dbOrder = database.find(order.getId());
// Check if order exists.
if (dbOrder == null) {
// Send reject message.
reject(order, "[ExecutionServer] Order id not found.");
continue;
}
// Send accepted for bidding message.
BEOrderUpdate.OrderUpdate response = BEOrderUpdate.OrderUpdate.newBuilder()
.setQtyRemaining((long) order.getQty())
.setStatus(OrderStatus.PENDING_REPLACE)
.setOrderId(order.getId())
.build();
session.write(response);
// Update order in the database for cancel
dbOrder.setPrice(order.getPrice());
dbOrder.setQty(order.getQty());
dbOrder.setReqType(order.getReqType());
dbOrder.setStatus(order.getStatus());
dbOrder.setOrderStatus(OrderStatus.PENDING_REPLACE);
database.update(dbOrder);
}
break;
case RequestTypes.REQUEST_CANCEL: {
ExecutionOrder dbOrder = database.find(order.getId());
// Check if order exists.
if (dbOrder == null) {
// Send accepted for bidding message.
reject(order, "[ExecutionServer] Order id not found.");
continue;
}
// Check if order is already filled.
if (dbOrder.getOrderStatus() == OrderStatus.FILLED) {
// Send accepted for bidding message.
reject(order, "[ExecutionServer] Order [ " + order.getId() + "] is filled can't be canceled.");
continue;
}
// Check if order is already filled.
if (dbOrder.getOrderStatus() == OrderStatus.CANCELED) {
// Send accepted for bidding message.
reject(order, "[ExecutionServer] Order [ " + order.getId() + "] is already canceled.");
continue;
}
// Send accepted for bidding message.
BEOrderUpdate.OrderUpdate response = BEOrderUpdate.OrderUpdate.newBuilder()
.setQtyRemaining((long) order.getQty())
.setStatus(OrderStatus.PENDING_CANCEL)
.setOrderId(order.getId())
.build();
session.write(response);
// Update order in the database for cancel
dbOrder.setReqType(order.getReqType());
dbOrder.setStatus(order.getStatus());
dbOrder.setOrderStatus(OrderStatus.PENDING_CANCEL);
database.update(dbOrder);
}
break;
}
}
ExecutionServerController.orderNotify();
}
}
}
}