package org.marketcetera.client; import java.beans.ExceptionListener; import java.math.BigDecimal; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.Deque; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; import javax.jms.JMSException; import javax.xml.bind.JAXBException; import org.apache.commons.lang.ObjectUtils; import org.marketcetera.client.brokers.BrokerStatus; import org.marketcetera.client.brokers.BrokersStatus; import org.marketcetera.client.config.SpringConfig; import org.marketcetera.client.jms.JmsManager; import org.marketcetera.client.jms.JmsUtils; import org.marketcetera.client.jms.OrderEnvelope; import org.marketcetera.client.jms.ReceiveOnlyHandler; import org.marketcetera.client.users.UserInfo; import org.marketcetera.core.ApplicationBase; import org.marketcetera.core.Util; import org.marketcetera.core.notifications.ServerStatusListener; import org.marketcetera.core.position.PositionKey; import org.marketcetera.metrics.ThreadedMetric; import org.marketcetera.trade.BrokerID; import org.marketcetera.trade.Currency; import org.marketcetera.trade.Equity; import org.marketcetera.trade.ExecutionReport; import org.marketcetera.trade.ExecutionReportImpl; import org.marketcetera.trade.FIXMessageWrapper; import org.marketcetera.trade.FIXOrder; import org.marketcetera.trade.Factory; import org.marketcetera.trade.Future; import org.marketcetera.trade.Hierarchy; import org.marketcetera.trade.Instrument; import org.marketcetera.trade.Option; import org.marketcetera.trade.Order; import org.marketcetera.trade.OrderBase; 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.ReportBase; import org.marketcetera.trade.ReportBaseImpl; import org.marketcetera.trade.TradeMessage; import org.marketcetera.trade.UserID; import org.marketcetera.util.except.ExceptUtils; import org.marketcetera.util.except.I18NException; import org.marketcetera.util.log.I18NBoundMessage1P; import org.marketcetera.util.log.I18NBoundMessage2P; import org.marketcetera.util.log.I18NBoundMessage4P; import org.marketcetera.util.log.SLF4JLoggerProxy; import org.marketcetera.util.misc.ClassVersion; import org.marketcetera.util.spring.SpringUtils; import org.marketcetera.util.ws.stateful.ClientContext; import org.marketcetera.util.ws.tags.SessionId; import org.marketcetera.util.ws.wrappers.DateWrapper; import org.marketcetera.util.ws.wrappers.RemoteException; import org.marketcetera.util.ws.wrappers.RemoteProxyException; import org.springframework.beans.BeansException; import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; import org.springframework.context.support.StaticApplicationContext; import org.springframework.jms.core.JmsOperations; import org.springframework.jms.listener.SimpleMessageListenerContainer; /* $License$ */ /** * The implementation of Client that connects to the server. * * @author anshul@marketcetera.com * @version $Id: ClientImpl.java 16888 2014-04-22 18:32:36Z colin $ * @since 1.0.0 */ @ClassVersion("$Id: ClientImpl.java 16888 2014-04-22 18:32:36Z colin $") public class ClientImpl implements Client, javax.jms.ExceptionListener { @Override public void sendOrder(OrderSingle inOrderSingle) throws ConnectionException, OrderValidationException { Validations.validate(inOrderSingle); convertAndSend(inOrderSingle); } @Override public void sendOrder(OrderReplace inOrderReplace) throws ConnectionException, OrderValidationException { Validations.validate(inOrderReplace); convertAndSend(inOrderReplace); } @Override public void sendOrder(OrderCancel inOrderCancel) throws ConnectionException, OrderValidationException { Validations.validate(inOrderCancel); convertAndSend(inOrderCancel); } @Override public void sendOrderRaw(FIXOrder inFIXOrder) throws ConnectionException, OrderValidationException { Validations.validate(inFIXOrder); convertAndSend(inFIXOrder); } @Override public void addReportListener(ReportListener inListener) { failIfClosed(); synchronized (mReportListeners) { mReportListeners.addFirst(inListener); } } @Override public void removeReportListener(ReportListener inListener) { failIfClosed(); synchronized (mReportListeners) { mReportListeners.removeFirstOccurrence(inListener); } } @Override public void addBrokerStatusListener (BrokerStatusListener listener) { failIfClosed(); synchronized (mBrokerStatusListeners) { mBrokerStatusListeners.addFirst(listener); } } @Override public void removeBrokerStatusListener (BrokerStatusListener listener) { failIfClosed(); synchronized (mBrokerStatusListeners) { mBrokerStatusListeners.removeFirstOccurrence(listener); } } @Override public void addServerStatusListener (ServerStatusListener listener) { failIfClosed(); synchronized (mServerStatusListeners) { mServerStatusListeners.addFirst(listener); } listener.receiveServerStatus(isServerAlive()); } @Override public void removeServerStatusListener (ServerStatusListener listener) { failIfClosed(); synchronized (mServerStatusListeners) { mServerStatusListeners.removeFirstOccurrence(listener); } } @Override public ReportBase[] getReportsSince (Date inDate) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { ReportBaseImpl[] reports = mService.getReportsSince(getServiceContext(),new DateWrapper(inDate)); return reports == null ? new ReportBase[0] : reports; } catch (RemoteException ex) { throw new ConnectionException(ex,Messages.ERROR_REMOTE_EXECUTION); } } @Override public BigDecimal getEquityPositionAsOf (Date inDate, Equity inEquity) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { return mService.getEquityPositionAsOf (getServiceContext(),new DateWrapper(inDate),inEquity); } catch (RemoteException ex) { throw new ConnectionException(ex,Messages.ERROR_REMOTE_EXECUTION); } } @Override public Map<PositionKey<Equity>, BigDecimal> getAllEquityPositionsAsOf (Date inDate) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { return mService.getAllEquityPositionsAsOf (getServiceContext(),new DateWrapper(inDate)).getMap(); } catch (RemoteException ex) { throw new ConnectionException(ex,Messages.ERROR_REMOTE_EXECUTION); } } @Override public BigDecimal getCurrencyPositionAsOf (Date inDate, Currency inCurrency) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { return mService.getCurrencyPositionAsOf (getServiceContext(),new DateWrapper(inDate),inCurrency); } catch (RemoteException ex) { throw new ConnectionException(ex,Messages.ERROR_REMOTE_EXECUTION); } } @Override public Map<PositionKey<Currency>, BigDecimal> getAllCurrencyPositionsAsOf (Date inDate) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { return mService.getAllCurrencyPositionsAsOf (getServiceContext(),new DateWrapper(inDate)).getMap(); } catch (RemoteException ex) { throw new ConnectionException(ex,Messages.ERROR_REMOTE_EXECUTION); } } /* (non-Javadoc) * @see org.marketcetera.client.Client#getAllFuturePositionsAsOf(java.util.Date) */ @Override public Map<PositionKey<Future>, BigDecimal> getAllFuturePositionsAsOf(Date inDate) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { return mService.getAllFuturePositionsAsOf(getServiceContext(), new DateWrapper(inDate)).getMap(); } catch (RemoteException ex) { throw new ConnectionException(ex, Messages.ERROR_REMOTE_EXECUTION); } } /* (non-Javadoc) * @see org.marketcetera.client.Client#getFuturePositionAsOf(java.util.Date, org.marketcetera.trade.Future) */ @Override public BigDecimal getFuturePositionAsOf(Date inDate, Future inFuture) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { return mService.getFuturePositionAsOf(getServiceContext(), new DateWrapper(inDate), inFuture); } catch (RemoteException ex) { throw new ConnectionException(ex, Messages.ERROR_REMOTE_EXECUTION); } } @Override public BigDecimal getOptionPositionAsOf(Date inDate, Option inOption) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { return mService.getOptionPositionAsOf( getServiceContext(), new DateWrapper(inDate), inOption); } catch (RemoteException ex) { throw new ConnectionException(ex, Messages.ERROR_REMOTE_EXECUTION); } } @Override public Map<PositionKey<Option>, BigDecimal> getAllOptionPositionsAsOf(Date inDate) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { return mService.getAllOptionPositionsAsOf (getServiceContext(), new DateWrapper(inDate)).getMap(); } catch (RemoteException ex) { throw new ConnectionException(ex, Messages.ERROR_REMOTE_EXECUTION); } } @Override public Map<PositionKey<Option>, BigDecimal> getOptionPositionsAsOf(Date inDate, String... inSymbols) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { return mService.getOptionPositionsAsOf (getServiceContext(), new DateWrapper(inDate), inSymbols).getMap(); } catch (RemoteException ex) { throw new ConnectionException(ex, Messages.ERROR_REMOTE_EXECUTION); } } @Override public String getUnderlying(String inOptionRoot) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { String value; synchronized (mUnderlyingToRootCache) { if(mUnderlyingToRootCache.containsKey(inOptionRoot)) { value = mUnderlyingToRootCache.get(inOptionRoot); } else { //cache null return values too value = mService.getUnderlying(getServiceContext(), inOptionRoot); mUnderlyingToRootCache.put(inOptionRoot, value); } } return value; } catch (RemoteException ex) { throw new ConnectionException(ex, Messages.ERROR_REMOTE_EXECUTION); } } @Override public Collection<String> getOptionRoots(String inUnderlying) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { Collection<String> value; synchronized (mRootToUnderlyingCache) { if(mRootToUnderlyingCache.containsKey(inUnderlying)) { value = mRootToUnderlyingCache.get(inUnderlying); } else { value = mService.getOptionRoots(getServiceContext(), inUnderlying); mRootToUnderlyingCache.put(inUnderlying, value); } } return value; } catch (RemoteException ex) { throw new ConnectionException(ex, Messages.ERROR_REMOTE_EXECUTION); } } @Override public BrokersStatus getBrokersStatus() throws ConnectionException { failIfClosed(); failIfDisconnected(); try { return mService.getBrokersStatus(getServiceContext()); } catch (RemoteException ex) { throw new ConnectionException(ex,Messages.ERROR_REMOTE_EXECUTION); } } @Override public UserInfo getUserInfo(UserID id, boolean useCache) throws ConnectionException { failIfClosed(); UserInfo result; synchronized (mUserInfoCache) { if (useCache) { result=mUserInfoCache.get(id); if (result!=null) { return result; } } failIfDisconnected(); try { result=mService.getUserInfo(getServiceContext(),id); } catch (RemoteException ex) { throw new ConnectionException (ex,Messages.ERROR_REMOTE_EXECUTION); } mUserInfoCache.put(id,result); } return result; } /* (non-Javadoc) * @see org.marketcetera.client.Client#addReport(org.marketcetera.trade.FIXMessageWrapper, org.marketcetera.trade.BrokerID, org.marketcetera.trade.Hierarchy) */ @Override public void addReport(FIXMessageWrapper inReport, BrokerID inBrokerID, Hierarchy inHierarchy) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { mService.addReport(getServiceContext(), inReport, inBrokerID, inHierarchy); } catch (RemoteException ex) { throw new ConnectionException(ex, Messages.ERROR_REMOTE_EXECUTION); } } /* (non-Javadoc) * @see org.marketcetera.client.Client#deleteReport(org.marketcetera.trade.ExecutionReportImpl) */ @Override public void deleteReport(ExecutionReportImpl inReport) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { mService.deleteReport(getServiceContext(),inReport); } catch (RemoteException ex) { throw new ConnectionException(ex, Messages.ERROR_REMOTE_EXECUTION); } } @Override public synchronized void close() { internalClose(); ClientManager.reset(); mClosed = true; } @Override public void reconnect() throws ConnectionException { reconnect(null); } @Override public synchronized void reconnect(ClientParameters inParameters) throws ConnectionException { failIfClosed(); if(mContext != null) { internalClose(); } if(inParameters != null) { setParameters(inParameters); } connect(); } @Override public void addExceptionListener(ExceptionListener inListener) { failIfClosed(); synchronized (mExceptionListeners) { mExceptionListeners.addFirst(inListener); } } @Override public void removeExceptionListener(ExceptionListener inListener) { failIfClosed(); synchronized (mExceptionListeners) { mExceptionListeners.removeFirstOccurrence(inListener); } } @Override public ClientParameters getParameters() { failIfClosed(); return new ClientParameters( mParameters.getUsername(), //hide the password value. "*****".toCharArray(), //$NON-NLS-1$ mParameters.getURL(), mParameters.getHostname(), mParameters.getPort(), mParameters.getIDPrefix()); } @Override public Date getLastConnectTime() { failIfClosed(); return mLastConnectTime; } @Override public boolean isCredentialsMatch(String inUsername, char[] inPassword) { return (!mClosed) && ObjectUtils.equals(mParameters.getUsername(), inUsername) && Arrays.equals(mParameters.getPassword(), inPassword); } @Override public boolean isServerAlive() { return ((!mClosed) && mServerAlive); } /* (non-Javadoc) * @see org.marketcetera.client.Client#getUserData() */ @Override public Properties getUserData() throws ConnectionException { failIfClosed(); failIfDisconnected(); try { return Util.propertiesFromString(mService.getUserData(getServiceContext())); } catch (RemoteException ex) { throw new ConnectionException(ex, Messages.ERROR_REMOTE_EXECUTION); } } /* (non-Javadoc) * @see org.marketcetera.client.Client#setUserData(java.util.Properties) */ @Override public void setUserData(Properties inProperties) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { mService.setUserData(getServiceContext(), Util.propertiesToString(inProperties)); } catch (RemoteException ex) { throw new ConnectionException(ex, Messages.ERROR_REMOTE_EXECUTION); } } /* (non-Javadoc) * @see org.marketcetera.client.Client#resolveSymbol(java.lang.String) */ @Override public Instrument resolveSymbol(String inSymbol) throws ConnectionException { failIfClosed(); failIfDisconnected(); try { return mService.resolveSymbol(getServiceContext(), inSymbol); } catch (RemoteException ex) { throw new ConnectionException(ex, Messages.ERROR_REMOTE_EXECUTION); } } /* (non-Javadoc) * @see org.marketcetera.client.Client#getOpenOrders() */ @Override public List<ReportBaseImpl> getOpenOrders() throws ConnectionException { failIfClosed(); failIfDisconnected(); try { return mService.getOpenOrders(getServiceContext()); } catch (RemoteException ex) { throw new ConnectionException(ex, Messages.ERROR_REMOTE_EXECUTION); } } /* (non-Javadoc) * @see org.marketcetera.client.Client#findRootOrderIdFor(org.marketcetera.trade.OrderID) */ @Override public OrderID findRootOrderIdFor(OrderID inOrderID) { failIfClosed(); failIfDisconnected(); try { return mService.getRootOrderIdFor(getServiceContext(), inOrderID); } catch (RemoteException ex) { throw new ConnectionException(ex, Messages.ERROR_REMOTE_EXECUTION); } } /** * Creates an instance given the parameters and connects to the server. * * @param inParameters the parameters to connect to the server, cannot * be null. * * @throws ConnectionException if there were errors connecting * to the server. */ public ClientImpl(ClientParameters inParameters) throws ConnectionException { setParameters(inParameters); connect(); } // TradeMessage reception; public scope required by Spring. public class TradeMessageReceiver implements ReceiveOnlyHandler<TradeMessage> { @Override public void receiveMessage (TradeMessage inReport) { if (inReport instanceof ExecutionReport) { notifyExecutionReport((ExecutionReport)inReport); } else if (inReport instanceof OrderCancelReject) { notifyCancelReject((OrderCancelReject)inReport); } else { Messages.LOG_RECEIVED_FIX_REPORT.warn (this,ObjectUtils.toString(inReport)); } } } void notifyExecutionReport(ExecutionReport inReport) { SLF4JLoggerProxy.debug(TRAFFIC, "Received Exec Report:{}", inReport); //$NON-NLS-1$ synchronized (mReportListeners) { for(ReportListener listener: mReportListeners) { try { listener.receiveExecutionReport(inReport); } catch (Throwable t) { Messages.LOG_ERROR_RECEIVE_EXEC_REPORT.warn(this, t, ObjectUtils.toString(inReport)); ExceptUtils.interrupt(t); } } } } void notifyCancelReject(OrderCancelReject inReport) { SLF4JLoggerProxy.debug(TRAFFIC, "Received Cancel Reject:{}", inReport); //$NON-NLS-1$ synchronized (mReportListeners) { for(ReportListener listener: mReportListeners) { try { listener.receiveCancelReject(inReport); } catch (Throwable t) { Messages.LOG_ERROR_RECEIVE_CANCEL_REJECT.warn(this, t, ObjectUtils.toString(inReport)); ExceptUtils.interrupt(t); } } } } // ReceiveOnlyHandler<BrokerStatus>; public scope required by Spring. public class BrokerStatusReceiver implements ReceiveOnlyHandler<BrokerStatus> { @Override public void receiveMessage (BrokerStatus status) { notifyBrokerStatus(status); } } void notifyBrokerStatus(BrokerStatus status) { SLF4JLoggerProxy.debug (TRAFFIC,"Received Broker Status:{}",status); //$NON-NLS-1$ synchronized (mBrokerStatusListeners) { for (BrokerStatusListener listener: mBrokerStatusListeners) { try { listener.receiveBrokerStatus(status); } catch (Throwable t) { Messages.LOG_ERROR_RECEIVE_BROKER_STATUS.warn(this, t, ObjectUtils.toString(status)); ExceptUtils.interrupt(t); } } } } void notifyServerStatus(boolean status) { SLF4JLoggerProxy.debug (TRAFFIC,"Received Server Status:{}",status); //$NON-NLS-1$ synchronized (mServerStatusListeners) { for (ServerStatusListener listener: mServerStatusListeners) { try { listener.receiveServerStatus(status); } catch (Throwable t) { Messages.LOG_ERROR_RECEIVE_SERVER_STATUS.warn(this, t, status); ExceptUtils.interrupt(t); } } } } // javax.jms.ExceptionListener. @Override public void onException(JMSException e) { exceptionThrown(new ConnectionException (e,Messages.ERROR_RECEIVING_JMS_MESSAGE)); } void exceptionThrown(ConnectionException inException) { synchronized (mExceptionListeners) { for(ExceptionListener l: mExceptionListeners) { try { l.exceptionThrown(inException); } catch (Exception e) { Messages.LOG_ERROR_NOTIFY_EXCEPTION.warn(this, e, ObjectUtils.toString(inException)); ExceptUtils.interrupt(e); } } } } /** * Fetches the next orderID base from server. * * @return the next orderID base from server. * * @throws RemoteException if there were communication errors. */ protected String getNextServerID() throws RemoteException { failIfDisconnected(); return mService.getNextOrderID(getServiceContext()); } /** * The 'heart' that produces heartbeats, keeping the connection to * the ORS server alive. */ private class Heart extends Thread { private volatile boolean mMarked; Heart() { super(Thread.currentThread().getThreadGroup(), Messages.HEARTBEAT_THREAD_NAME.getText()); setDaemon(true); } void markExit() { mMarked=true; } private boolean isMarked() { return mMarked; } @Override public void run() { while(true) { try { Thread.sleep(mParameters.getHeartbeatInterval()); } catch (InterruptedException ex) { SLF4JLoggerProxy.debug(HEARTBEATS, "Stopped (interrupted)"); //$NON-NLS-1$ markExit(); setServerAlive(false); return; } if(isMarked()) { SLF4JLoggerProxy.debug(HEARTBEATS, "Stopped (marked)"); //$NON-NLS-1$ setServerAlive(false); return; } try { heartbeat(); setServerAlive(true); } catch (Exception ex) { setServerAlive(false); if(ExceptUtils.isInterruptException(ex)) { SLF4JLoggerProxy.debug(HEARTBEATS, "Stopped (interrupted)"); //$NON-NLS-1$ markExit(); return; } SLF4JLoggerProxy.debug(HEARTBEATS, ex, "Failed"); //$NON-NLS-1$ exceptionThrown(new ConnectionException(ex, Messages.ERROR_HEARTBEAT_FAILED)); if(ex instanceof RemoteException) { // We connected to the server, but the session may have expired: attempt to auto-reconnect // after a short delay to let the server settle (if it has just restarted). The // delay is random so that not all clients will try and contact the ORS at the same time. long delay = (long)(RECONNECT_WAIT_INTERVAL*(0.75+1.25*Math.random())); SLF4JLoggerProxy.debug(HEARTBEATS, "Reconnecting in {} ms", //$NON-NLS-1$ delay); try { Thread.sleep(delay); } catch (InterruptedException ex2) { SLF4JLoggerProxy.debug(HEARTBEATS, "Stopped (interrupted)"); //$NON-NLS-1$ markExit(); return; } try { reconnectWebServices(); setServerAlive(true); SLF4JLoggerProxy.debug(HEARTBEATS, "...reconnect succeeded."); //$NON-NLS-1$ } catch (Exception ex2) { setServerAlive(false); if(ExceptUtils.isInterruptException(ex2)) { SLF4JLoggerProxy.debug(HEARTBEATS, "Stopped (interrupted)"); //$NON-NLS-1$ markExit(); return; } SLF4JLoggerProxy.debug(HEARTBEATS, ex2, "...reconnect failed."); //$NON-NLS-1$ exceptionThrown(new ConnectionException(ex2, Messages.ERROR_HEARTBEAT_FAILED)); } } } if(isMarked()) { SLF4JLoggerProxy.debug(HEARTBEATS, "Stopped (marked)"); //$NON-NLS-1$ setServerAlive(false); return; } } } } private void internalClose() { if (mContext == null) { return; } //Clear all the caches synchronized (mUnderlyingToRootCache) { mUnderlyingToRootCache.clear(); } synchronized (mRootToUnderlyingCache) { mRootToUnderlyingCache.clear(); } synchronized (mUserInfoCache) { mUserInfoCache.clear(); } // Close the heartbeat generator first so that it won't // re-create a JMS connection during subsequent shutdown. In // fact, the generator will normally shut down the JMS // connection before it terminates. if (mHeart!=null) { mHeart.markExit(); mHeart.interrupt(); try { mHeart.join(); } catch (InterruptedException ex) { SLF4JLoggerProxy.debug (this,"Error when joining with heartbeat thread",ex); //$NON-NLS-1$ ExceptUtils.interrupt(ex); } } setServerAlive(false); try { closeWebServices(); } catch (Exception ex) { SLF4JLoggerProxy.debug (this,"Error when closing web service client",ex); //$NON-NLS-1$ ExceptUtils.interrupt(ex); } finally { try { if (mContext!=null) { mContext.close(); } } catch (Exception ex) { SLF4JLoggerProxy.debug (this,"Error when closing context",ex); //$NON-NLS-1$ ExceptUtils.interrupt(ex); } finally { setContext(null); } } } /** * Connects the web services. * * @throws I18NException if an error occurs connecting * @throws RemoteException if an error occurs connecting */ protected void connectWebServices() throws I18NException, RemoteException { mServiceClient = new org.marketcetera.util.ws.stateful.Client(mParameters.getHostname(), mParameters.getPort(), ClientVersion.APP_ID); mServiceClient.login(mParameters.getUsername(), mParameters.getPassword()); mService = mServiceClient.getService(Service.class); } /** * Closes the web service. * * @throws RemoteException if an error occurs closing */ protected void closeWebServices() throws RemoteException { if(mServiceClient != null) { mServiceClient.logout(); } mServiceClient = null; } /** * Reconnects the web service. * * @throws RemoteException if an error occurs reconnecting */ protected void reconnectWebServices() throws RemoteException { mServiceClient.logout(); mServiceClient.login(mParameters.getUsername(), mParameters.getPassword()); } /** * Connects the JMS service. * * @throws JAXBException if an error occurs connecting to the JMS service */ protected void connectJms() throws JAXBException { SpringConfig cfg = SpringConfig.getSingleton(); mJmsMgr = new JmsManager(cfg.getIncomingConnectionFactory(), cfg.getOutgoingConnectionFactory(), this); startJms(); } /** * Executes a heartbeat. * * @throws RemoteException if the heartbeat cannot be executed */ protected void heartbeat() throws RemoteException { mService.heartbeat(getServiceContext()); } /** * Gets the session ID value. * * @return a <code>SessionId</code> value */ protected SessionId getSessionId() { return getServiceContext().getSessionId(); } /** * Starts the JMS connection. * * @throws JAXBException if an error occurs starting the JMS connection */ protected void startJms() throws JAXBException { if(mToServer != null) { return; } mTradeMessageListener = mJmsMgr.getIncomingJmsFactory().registerHandlerTMX(new TradeMessageReceiver(), JmsUtils.getReplyTopicName(getSessionId()), true); mTradeMessageListener.start(); mBrokerStatusListener = mJmsMgr.getIncomingJmsFactory().registerHandlerBSX(new BrokerStatusReceiver(), Service.BROKER_STATUS_TOPIC, true); mBrokerStatusListener.start(); mToServer = mJmsMgr.getOutgoingJmsFactory().createJmsTemplateX(Service.REQUEST_QUEUE, false); } /** * Connects the client to the server. * * @throws ConnectionException if an error occurs connecting to the server */ private void connect() throws ConnectionException { if(mParameters.getURL() == null || mParameters.getURL().trim().isEmpty()) { throw new ConnectionException(Messages.CONNECT_ERROR_NO_URL); } if(mParameters.getUsername() == null || mParameters.getUsername().trim().isEmpty()) { throw new ConnectionException(Messages.CONNECT_ERROR_NO_USERNAME); } if(mParameters.getHostname() == null || mParameters.getHostname().trim().isEmpty()) { throw new ConnectionException(Messages.CONNECT_ERROR_NO_HOSTNAME); } if(mParameters.getPort() < 1 || mParameters.getPort() > 0xFFFF) { throw new ConnectionException(new I18NBoundMessage1P(Messages.CONNECT_ERROR_INVALID_PORT, mParameters.getPort())); } try { StaticApplicationContext parentCtx = new StaticApplicationContext(); SpringUtils.addStringBean(parentCtx,"brokerURL",mParameters.getURL()); //$NON-NLS-1$ SpringUtils.addStringBean(parentCtx,"runtimeUsername",mParameters.getUsername()); //$NON-NLS-1$ SpringUtils.addStringBean(parentCtx,"runtimePassword",mParameters==null?null: String.valueOf(mParameters.getPassword())); //$NON-NLS-1$ parentCtx.refresh(); AbstractApplicationContext ctx; try { ctx = new FileSystemXmlApplicationContext(new String[] { "file:"+ApplicationBase.CONF_DIR+"client.xml" }, //$NON-NLS-1$ parentCtx); } catch (BeansException e) { ctx = new ClassPathXmlApplicationContext(new String[] { "client.xml" }, //$NON-NLS-1$ parentCtx); } ctx.registerShutdownHook(); ctx.start(); setContext(ctx); SpringConfig cfg = SpringConfig.getSingleton(); if(cfg == null) { throw new ConnectionException(Messages.CONNECT_ERROR_NO_CONFIGURATION); } connectWebServices(); connectJms(); mServerAlive = true; notifyServerStatus(true); mHeart = new Heart(); mHeart.start(); ClientIDFactory idFactory = new ClientIDFactory(mParameters.getIDPrefix(), this); idFactory.init(); Factory.getInstance().setOrderIDFactory(idFactory); } catch(Exception e) { internalClose(); ExceptUtils.interrupt(e); if(e.getCause() instanceof RemoteProxyException) { RemoteProxyException ex = (RemoteProxyException)e.getCause(); if(IncompatibleComponentsException.class.getName().equals(ex.getServerName())) { throw new ConnectionException(e, new I18NBoundMessage1P(Messages.ERROR_CONNECT_INCOMPATIBLE_DEDUCED, ex.getMessage())); } } else if(e.getCause() instanceof IncompatibleComponentsException) { IncompatibleComponentsException ex = (IncompatibleComponentsException)e.getCause(); throw new ConnectionException(e, new I18NBoundMessage2P(Messages.ERROR_CONNECT_INCOMPATIBLE_DIRECT, ClientVersion.APP_ID, ex.getServerVersion())); } throw new ConnectionException(e, new I18NBoundMessage4P(Messages.ERROR_CONNECT_TO_SERVER, mParameters.getURL(), mParameters.getUsername(), mParameters.getHostname(), mParameters.getPort())); } mLastConnectTime = new Date(); } private void setContext(AbstractApplicationContext inContext) { mContext = inContext; } private void convertAndSend(Order inOrder) throws ConnectionException { ThreadedMetric.event("client-OUT", //$NON-NLS-1$ inOrder instanceof OrderBase ? ((OrderBase) inOrder).getOrderID() : null); failIfClosed(); SLF4JLoggerProxy.debug(TRAFFIC, "Sending order:{}", inOrder); //$NON-NLS-1$ try { if (mToServer == null) { throw new ClientInitException(Messages.NOT_CONNECTED_TO_SERVER); } failIfDisconnected(); SpringConfig cfg = SpringConfig.getSingleton(); Collection<OrderModifier> orderModifiers = cfg.getOrderModifiers(); for(OrderModifier modifier : orderModifiers) { modifier.modify(inOrder); } mToServer.convertAndSend(new OrderEnvelope(inOrder, getSessionId())); } catch (Exception e) { ConnectionException exception; exception = new ConnectionException(e, new I18NBoundMessage1P( Messages.ERROR_SEND_MESSAGE, ObjectUtils.toString(inOrder))); Messages.LOG_ERROR_SEND_EXCEPTION.warn(this, exception, ObjectUtils.toString(inOrder)); ExceptUtils.interrupt(e); exceptionThrown(exception); throw exception; } } /** * Checks to see if the client is closed and fails if the client * is closed. * * @throws IllegalStateException if the client is closed. */ private void failIfClosed() throws IllegalStateException { if(mClosed) { throw new IllegalStateException(Messages.CLIENT_CLOSED.getText()); } } /** * Asserts that the client's connection to the server is alive; * fails otherwise. * * @throws IllegalStateException if the server connection is dead. */ private void failIfDisconnected() throws IllegalStateException { if(!isServerAlive()) { throw new IllegalStateException (Messages.SERVER_CONNECTION_DEAD.getText()); } } private ClientContext getServiceContext() { return mServiceClient.getContext(); } /** * Sets the client parameters value. * * @param inParameters the client parameters, cannot be null. */ private void setParameters(ClientParameters inParameters) { if(inParameters == null) { throw new NullPointerException(); } mParameters = inParameters; } private void stopJms() { if (mToServer==null) { return; } try { if (mTradeMessageListener!=null) { mTradeMessageListener.shutdown(); } } catch (Exception ex) { SLF4JLoggerProxy.debug (this,"Error when closing trade message listener",ex); //$NON-NLS-1$ ExceptUtils.interrupt(ex); } finally { try { if (mBrokerStatusListener!=null) { mBrokerStatusListener.shutdown(); } } catch (Exception ex) { SLF4JLoggerProxy.debug (this,"Error when closing broker status listener",ex); //$NON-NLS-1$ ExceptUtils.interrupt(ex); } finally { mToServer = null; } } } /** * Sets the server connection status. If the status changed, the * registered callbacks are invoked. * * @param serverAlive True means the server connection is alive. */ private void setServerAlive(boolean serverAlive) { if (mServerAlive==serverAlive) { return; } if (serverAlive) { try { startJms(); } catch (JAXBException ex) { exceptionThrown(new ConnectionException (ex,Messages.ERROR_CREATING_JMS_CONNECTION)); return; } } else { stopJms(); } mServerAlive=serverAlive; notifyServerStatus(isServerAlive()); } private volatile AbstractApplicationContext mContext; private volatile JmsManager mJmsMgr; private volatile SimpleMessageListenerContainer mTradeMessageListener; private volatile SimpleMessageListenerContainer mBrokerStatusListener; private volatile JmsOperations mToServer; protected volatile ClientParameters mParameters; private volatile boolean mClosed = false; private volatile boolean mServerAlive = false; private final Deque<ReportListener> mReportListeners = new LinkedList<ReportListener>(); private final Deque<BrokerStatusListener> mBrokerStatusListeners= new LinkedList<BrokerStatusListener>(); private final Deque<ServerStatusListener> mServerStatusListeners= new LinkedList<ServerStatusListener>(); private final Deque<ExceptionListener> mExceptionListeners = new LinkedList<ExceptionListener>(); private Date mLastConnectTime; private final Map<UserID,UserInfo> mUserInfoCache= new HashMap<UserID,UserInfo>(); private final Map<String,String> mUnderlyingToRootCache= new HashMap<String, String>(); private final Map<String,Collection<String>> mRootToUnderlyingCache= new HashMap<String, Collection<String>>(); private static final long RECONNECT_WAIT_INTERVAL = 10000; private volatile org.marketcetera.util.ws.stateful.Client mServiceClient; private Service mService; private Heart mHeart; private static final String TRAFFIC = ClientImpl.class.getPackage(). getName() + ".traffic"; //$NON-NLS-1$ private static final String HEARTBEATS = ClientImpl.class.getPackage(). getName() + ".heartbeats"; //$NON-NLS-1$ }