package org.marketcetera.photon;
import java.util.Vector;
import org.apache.log4j.Logger;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.WorkbenchException;
import org.marketcetera.client.Client;
import org.marketcetera.client.ClientInitException;
import org.marketcetera.client.ClientManager;
import org.marketcetera.client.ConnectionException;
import org.marketcetera.client.OrderValidationException;
import org.marketcetera.client.ReportListener;
import org.marketcetera.core.ClassVersion;
import org.marketcetera.core.NoMoreIDsException;
import org.marketcetera.event.HasFIXMessage;
import org.marketcetera.messagehistory.MessageVisitor;
import org.marketcetera.messagehistory.ReportHolder;
import org.marketcetera.messagehistory.TradeReportsHistory;
import org.marketcetera.quickfix.FIXMessageUtil;
import org.marketcetera.quickfix.MarketceteraFIXException;
import org.marketcetera.trade.BrokerID;
import org.marketcetera.trade.ExecutionReport;
import org.marketcetera.trade.FIXOrder;
import org.marketcetera.trade.Factory;
import org.marketcetera.trade.Instrument;
import org.marketcetera.trade.Order;
import org.marketcetera.trade.OrderCancel;
import org.marketcetera.trade.OrderCancelReject;
import org.marketcetera.trade.OrderID;
import org.marketcetera.trade.OrderReplace;
import org.marketcetera.trade.OrderSingle;
import org.marketcetera.trade.OrderStatus;
import org.marketcetera.trade.ReportBase;
import org.marketcetera.util.except.I18NException;
import org.marketcetera.util.log.I18NBoundMessage1P;
import quickfix.FieldNotFound;
/* $License$ */
/**
* OrderManager is the main repository for business logic. It can be considered
* the "controller" in a standard model-view-controller architecture. The main entry
* points are the <code>handle*</code> methods for handling incoming and outgoing
* messages of various types.
*
* @author gmiller
* @version $Id: PhotonController.java 16495 2013-03-09 04:45:03Z colin $
* @since 1.0.0
*/
@ClassVersion("$Id: PhotonController.java 16495 2013-03-09 04:45:03Z colin $")
public class PhotonController
implements Messages, ReportListener
{
private Logger internalMainLogger = PhotonPlugin.getMainConsoleLogger();
private TradeReportsHistory fixMessageHistory;
public static final BrokerID DEFAULT_BROKER = null;
public void setMessageHistory(TradeReportsHistory fixMessageHistory)
{
this.fixMessageHistory = fixMessageHistory;
}
@Override
public void receiveCancelReject(OrderCancelReject inReport) {
checkReportID(inReport);
fixMessageHistory.addIncomingMessage(inReport);
try {
handleCancelReject(inReport);
} catch (FieldNotFound e) {
MarketceteraFIXException mfix = MarketceteraFIXException.createFieldNotFoundException(e);
internalMainLogger.error(CANNOT_DECODE_INCOMING_SPECIFIED_MESSAGE.getText(mfix.getMessage()),
mfix);
}
}
@Override
public void receiveExecutionReport(ExecutionReport inReport) {
checkReportID(inReport);
fixMessageHistory.addIncomingMessage(inReport);
try {
handleExecutionReport(inReport);
} catch (NoMoreIDsException e) {
internalMainLogger.error(CANNOT_DECODE_INCOMING_MESSAGE.getText(),
e);
} catch (FieldNotFound e) {
MarketceteraFIXException mfix = MarketceteraFIXException.createFieldNotFoundException(e);
internalMainLogger.error(CANNOT_DECODE_INCOMING_SPECIFIED_MESSAGE.getText(mfix.getMessage()),
mfix);
}
}
private void checkReportID(ReportBase report) {
if (report.getReportID() == null) {
internalMainLogger.error(PHOTON_CONTROLLER_MISSING_REPORT_ID.getText(report.toString()));
}
}
protected void asyncExec(Runnable runnable) {
Display.getDefault().asyncExec(runnable);
}
protected void handleExecutionReport(ExecutionReport inReport) throws FieldNotFound, NoMoreIDsException {
if (OrderStatus.Rejected == inReport.getOrderStatus()) {
// TODO: improve ExecutionReport API to expose encoded text
String rejectReason = inReport.getText();
if(rejectReason == null) {
if (inReport instanceof HasFIXMessage) {
rejectReason = FIXMessageUtil.getTextOrEncodedText(((HasFIXMessage) inReport).getMessage(), Messages.UNKNOWN_VALUE.getText());
} else {
rejectReason = Messages.UNKNOWN_VALUE.getText();
}
}
org.marketcetera.trade.OrderID orderID = inReport.getOrderID();
Instrument instrument = inReport.getInstrument();
String rejectMsg = REJECT_MESSAGE.getText(orderID.getValue(),
instrument == null ? Messages.UNKNOWN_VALUE.getText() : instrument.getSymbol(),
rejectReason);
internalMainLogger.error(rejectMsg);
}
}
protected void handleCancelReject(OrderCancelReject inReport) throws FieldNotFound {
String reason = null;
String text = inReport.getText();
if(text == null) {
text = Messages.UNKNOWN_VALUE.getText();
}
String origClOrdID = Messages.UNKNOWN_VALUE.getText();
org.marketcetera.trade.OrderID oID = inReport.getOriginalOrderID();
if(oID != null) {
origClOrdID = oID.getValue();
}
String errorMsg = CANCEL_REJECT_MESSAGE.getText(origClOrdID,
(text == null ? 0 : 1),
text,
(reason == null ? 0 : 1),
reason);
internalMainLogger.error(errorMsg);
}
public void cancelOneOrderByClOrdID(String clOrdID) throws NoMoreIDsException {
OrderID orderid = new OrderID(clOrdID);
ExecutionReport report = getReportForCancel(orderid);
if (report != null) {
if (internalMainLogger.isDebugEnabled()) {
internalMainLogger
.debug("Exec id for cancel execution report:" + report.getExecutionID()); //$NON-NLS-1$
}
OrderCancel cancel = Factory.getInstance().createOrderCancel(report);
/*
* Remove the broker order id since some of our reports have "NONE"
* which is an invalid value.
*/
cancel.setBrokerOrderID(null);
sendOrder(cancel);
} else {
internalMainLogger.error(CANNOT_SEND_CANCEL.getText(clOrdID));
return;
}
}
private ExecutionReport getReportForCancel(OrderID orderid) {
ReportHolder firstReportHolder = fixMessageHistory.getFirstReport(orderid);
ExecutionReport report;
if (firstReportHolder != null) {
report = (ExecutionReport) firstReportHolder.getReport();
} else {
report = fixMessageHistory.getLatestExecutionReport(orderid);
}
return report;
}
public void replaceOrder(ExecutionReport report) throws WorkbenchException {
ExecutionReport originalReport = getReportForCancel(report.getOrderID());
if (originalReport != null) {
OrderReplace replace = Factory.getInstance().createOrderReplace(
originalReport);
replace.setPrice(report.getPrice());
replace.setBrokerOrderID(report.getBrokerOrderID());
PhotonPlugin.getDefault().showOrderInTicket(replace);
}
}
/** Panic button: cancel all open orders
* Need to do the cancel in 2 phases: first collect all clOrderIds to cancel,
* then cancel them.
* Trying to cancel them while collecting results in a deadlock, since we are
* holding a read lock while collecting, and sending a cancel tries to acquire
* the write lock to add new messages to message history.
* @param monitor progress monitor
*/
public void cancelAllOpenOrders(IProgressMonitor monitor) {
final Vector<String> clOrdIdsToCancel = new Vector<String>();
fixMessageHistory.visitOpenOrdersExecutionReports(new MessageVisitor() {
public void visitOpenOrderExecutionReports(ReportBase report) {
String clOrdId = report.getOrderID().getValue();
if (clOrdId == null) {
internalMainLogger.error(CANNOT_SEND_CANCEL_FOR_REASON
.getText(clOrdId, report));
} else {
clOrdIdsToCancel.add(clOrdId);
}
}
});
monitor.beginTask(Messages.PHOTON_CONTROLLER_CANCEL_ALL_ORDERS_TASK
.getText(), clOrdIdsToCancel.size());
for (String clOrdId : clOrdIdsToCancel) {
try {
cancelOneOrderByClOrdID(clOrdId);
if (internalMainLogger.isDebugEnabled()) {
internalMainLogger.debug("cancelling order for " + clOrdId);} //$NON-NLS-1$
monitor.worked(1);
} catch (NoMoreIDsException ignored) {
// ignore
}
}
monitor.done();
}
public void sendOrder(Order inOrder) {
internalMainLogger.info(PHOTON_CONTROLLER_SENDING_MESSAGE.getText(inOrder.toString()));
if (ClientManager.isInitialized()){
try {
Client client = ClientManager.getInstance();
if(inOrder instanceof OrderSingle) {
client.sendOrder((OrderSingle) inOrder);
} else if(inOrder instanceof OrderReplace) {
client.sendOrder((OrderReplace)inOrder);
} else if(inOrder instanceof OrderCancel) {
client.sendOrder((OrderCancel)inOrder);
} else if(inOrder instanceof FIXOrder) {
client.sendOrderRaw((FIXOrder)inOrder);
} else {
internalMainLogger.error(SEND_ORDER_FAIL_UNKNOWN_TYPE.getText(inOrder.toString()));
}
} catch (OrderValidationException e){
internalMainLogger.error(SEND_ORDER_VALIDATION_FAILED.getText(inOrder.toString()), e);
} catch (ConnectionException ignore){
//ignore as the exception listener will be invoked
} catch (ClientInitException e) {
internalMainLogger.error(SEND_ORDER_NOT_INITIALIZED.getText(inOrder.toString()), e);
}
} else {
internalMainLogger.error(CANNOT_SEND_NOT_CONNECTED.getText());
}
}
public void sendOrderChecked(Order inOrder) throws I18NException {
internalMainLogger.info(PHOTON_CONTROLLER_SENDING_MESSAGE
.getText(inOrder.toString()));
try {
Client client = ClientManager.getInstance();
if (inOrder instanceof OrderSingle) {
client.sendOrder((OrderSingle) inOrder);
} else if (inOrder instanceof OrderReplace) {
client.sendOrder((OrderReplace) inOrder);
} else if (inOrder instanceof OrderCancel) {
client.sendOrder((OrderCancel) inOrder);
} else if (inOrder instanceof FIXOrder) {
client.sendOrderRaw((FIXOrder) inOrder);
} else {
throw new I18NException(new I18NBoundMessage1P(
SEND_ORDER_FAIL_UNKNOWN_TYPE, inOrder.toString()));
}
} catch (OrderValidationException e) {
throw new I18NException(new I18NBoundMessage1P(
SEND_ORDER_VALIDATION_FAILED, inOrder.toString()));
} catch (ClientInitException e) {
throw new I18NException(CANNOT_SEND_NOT_CONNECTED);
} catch (ConnectionException e) {
throw new I18NException(CANNOT_SEND_NOT_CONNECTED);
}
}
/**
* @return Returns the mainConsoleLogger.
*/
public Logger getMainConsoleLogger() {
return internalMainLogger;
}
/**
* @param mainConsoleLogger The mainConsoleLogger to set.
*/
public void setMainConsoleLogger(Logger mainConsoleLogger) {
this.internalMainLogger = mainConsoleLogger;
}
}