package sample;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.StringUtils;
import quickfix.Acceptor;
import quickfix.Application;
import quickfix.ConfigError;
import quickfix.DefaultSessionFactory;
import quickfix.Initiator;
import quickfix.MemoryStoreFactory;
import quickfix.MessageStoreFactory;
import quickfix.SLF4JLogFactory;
import quickfix.Session;
import quickfix.SessionFactory;
import quickfix.SessionID;
import quickfix.SessionSettings;
import quickfix.SocketAcceptor;
import quickfix.SocketInitiator;
import quickfix.ThreadedSocketAcceptor;
import quickfix.ThreadedSocketInitiator;
/**
* The QuickFixBean gives us a convenient way to boostrap quickfix from Spring.
*/
public class QuickFixBean implements DisposableBean, InitializingBean {
private Application application;
private SessionSettings sessionSettings;
private MessageStoreFactory messageStoreFactory;
private quickfix.LogFactory logFactory;
private boolean client = true;
private boolean threaded = false;
private Map sessions;
private HashMap jmxSessions;
private static Log log = LogFactory.getLog(QuickFixBean.class);
public void afterPropertiesSet() throws ConfigError {
validateProperties();
SessionFactory sessionFactory = new DefaultSessionFactory(application, messageStoreFactory, logFactory);
if (client) {
Initiator initiator;
if (threaded) {
initiator = new ThreadedSocketInitiator(sessionFactory, sessionSettings);
} else {
initiator = new SocketInitiator(sessionFactory, sessionSettings);
}
initiator.start();
} else {
Acceptor acceptor;
if (threaded) {
acceptor = new ThreadedSocketAcceptor(sessionFactory, sessionSettings);
} else {
acceptor = new SocketAcceptor(sessionFactory, sessionSettings);
}
acceptor.start();
}
lookupSessions();
visitSessions(new SessionVisitor() {
public void visitSession(SessionID sessionId, Session session) {
log.info("Started " + sessionId);
}});
}
private void validateProperties() {
if (application == null) {
throw new BeanCreationException("Must specify an application");
}
if (sessionSettings == null) {
throw new BeanCreationException("Must specify session settings");
}
if (logFactory == null) {
logFactory = new SLF4JLogFactory(sessionSettings);
}
if (messageStoreFactory == null) {
messageStoreFactory = new MemoryStoreFactory();
}
}
public void destroy() {
visitSessions(new SessionVisitor() {
public void visitSession(SessionID sessionId, Session session) {
session.logout();
}});
visitSessions(new SessionVisitor() {
public void visitSession(SessionID sessionId, Session session) {
int waitCount = 1;
while (waitCount < 5 && session.isLoggedOn()) {
log.info("Waiting for " + sessionId + " to logout");
try {
Thread.sleep(200 * waitCount);
} catch (InterruptedException e) {
break;
}
waitCount++;
}
if (session.isLoggedOn()) {
log.warn(sessionId + " didn't log out, shutting down anyway.");
}
}});
}
private void lookupSessions() {
sessions = new HashMap();
jmxSessions = new HashMap();
for (Iterator iter = sessionSettings.sectionIterator(); iter.hasNext();) {
SessionID sessionId = (SessionID) iter.next();
Session session = Session.lookupSession(sessionId);
if (session != null) {
sessions.put(sessionId, session);
jmxSessions.put("quickfixj:name=" + StringUtils.replace(sessionId.toString(), ":", ""), session);
}
}
}
/**
* This is just to avoid duplicating the loop code.
* No it isn't really the Visitor pattern. No it probably isn't necessary.
* @param sessionVisitor Does something to each session.
* @see SessionVisitor#visitSession(SessionID, Session)
*/
private void visitSessions(SessionVisitor sessionVisitor) {
for (Iterator iter = sessions.entrySet().iterator(); iter.hasNext();) {
Entry entry = (Entry) iter.next();
SessionID sessionId = (SessionID) entry.getKey();
Session session = (Session) entry.getValue();
if (sessionId != null && session != null) {
sessionVisitor.visitSession(sessionId, session);
}
}
}
private interface SessionVisitor {
/**
* Do something to the given session.
* @param sessionId
* @param session
*/
public void visitSession(SessionID sessionId, Session session);
}
public Map getSessions() {
return jmxSessions;
}
public Application getApplication() {
return application;
}
public void setApplication(Application application) {
this.application = application;
}
public boolean isClient() {
return client;
}
public void setClient(boolean client) {
this.client = client;
}
public quickfix.LogFactory getLogFactory() {
return logFactory;
}
public void setLogFactory(quickfix.LogFactory logFactory) {
this.logFactory = logFactory;
}
public MessageStoreFactory getMessageStoreFactory() {
return messageStoreFactory;
}
public void setMessageStoreFactory(MessageStoreFactory messageStoreFactory) {
this.messageStoreFactory = messageStoreFactory;
}
public SessionSettings getSessionSettings() {
return sessionSettings;
}
public void setSessionSettings(SessionSettings sessionSettings) {
this.sessionSettings = sessionSettings;
}
public boolean isThreaded() {
return threaded;
}
public void setThreaded(boolean threaded) {
this.threaded = threaded;
}
}
Whilst Spring is great for wiring up an application I don't like domain configuration data there and
I'd not want my support guys having to edit XML for each session as its too error prone.
A more natural home for all the session stuff is via a DAO layer with both database and file based implementations - the file one could in fact be other Spring contexts containing stuff like above. The database implplementation could use Hibernate and then both of them share the domain configuration data objects, e.g. SessionConfig objects. Spring would then wire up which DAO implementation you'd have.
Any configuration GUI would then deal with DAO but use the database in a large installation whilst small ones could get manual with XML. The file based DAO would not support editing of the configuration data whilst the database implementation would.
Of course there are also all the nice enterprise features of a database to...