Index: src/test/java/quickfix/JdbcLogTest.java =================================================================== --- src/test/java/quickfix/JdbcLogTest.java (revision 576) +++ src/test/java/quickfix/JdbcLogTest.java (working copy) @@ -27,31 +27,35 @@ import junit.framework.TestCase; public class JdbcLogTest extends TestCase { - protected void tearDown() throws Exception { - JdbcTestSupport.assertNoActiveConnections(); - super.tearDown(); - } + private JdbcLog log; + private JdbcLogFactory logFactory; + private Connection connection; + private SessionID sessionID; - public void testLog() throws Exception { - Connection connection = JdbcTestSupport.getConnection(); + protected void setUp() throws Exception { + super.setUp(); + connection = JdbcTestSupport.getConnection(); SessionSettings settings = new SessionSettings(); JdbcTestSupport.setHypersonicSettings(settings); initializeTableDefinitions(connection); - JdbcLogFactory logFactory = new JdbcLogFactory(settings); + logFactory = new JdbcLogFactory(settings); long now = System.currentTimeMillis(); - SessionID sessionID = new SessionID("FIX.4.2", "SENDER-" + now, "TARGET-" + now); + sessionID = new SessionID("FIX.4.2", "SENDER-" + now, "TARGET-" + now); settings.setString(sessionID, "ConnectionType", "acceptor"); - JdbcLog log = (JdbcLog) logFactory.create(sessionID); + log = (JdbcLog) logFactory.create(sessionID); + assertEquals(0, getRowCount(connection, JdbcLog.MESSAGES_LOG_TABLE)); + } + public void testLog() throws Exception { assertEquals(0, getRowCount(connection, "messages_log")); log.onIncoming("INCOMING"); assertEquals(1, getRowCount(connection, "messages_log")); - assertLogData(connection, 0, sessionID, "INCOMING", "messages_log"); + assertLogData(connection, 0, sessionID, "INCOMING", JdbcLog.MESSAGES_LOG_TABLE); log.onOutgoing("OUTGOING"); - assertEquals(2, getRowCount(connection, "messages_log")); - assertLogData(connection, 0, sessionID, "INCOMING", "messages_log"); - assertLogData(connection, 1, sessionID, "OUTGOING", "messages_log"); + assertEquals(2, getRowCount(connection, JdbcLog.MESSAGES_LOG_TABLE)); + assertLogData(connection, 0, sessionID, "INCOMING", JdbcLog.MESSAGES_LOG_TABLE); + assertLogData(connection, 1, sessionID, "OUTGOING", JdbcLog.MESSAGES_LOG_TABLE); assertEquals(0, getRowCount(connection, "event_log")); log.onEvent("EVENT"); @@ -59,10 +63,37 @@ assertLogData(connection, 0, sessionID, "EVENT", "event_log"); log.clear(); - assertEquals(0, getRowCount(connection, "messages_log")); + assertEquals(0, getRowCount(connection, JdbcLog.MESSAGES_LOG_TABLE)); assertEquals(0, getRowCount(connection, "event_log")); } + /** Make sure the logger handles the situation where the underlying JdbcLog is misconfigured + * (such as we can't connect ot the DB, or the tables are missing) and doesn't try + * to print failing exceptions recursively until the stack overflows + */ + public void testHandlesRecursivelyFailingException() throws Exception { + // need to register the session since we are going to log errors through LogUtil + Session.registerSession(new Session(new UnitTestApplication(), new MemoryStoreFactory(), sessionID, + new DataDictionary("FIX42.xml"), null, logFactory, new DefaultMessageFactory(), 0)); + + // remove the messages and events tables + connection.prepareStatement("DROP TABLE IF EXISTS "+JdbcLog.MESSAGES_LOG_TABLE+";").execute(); + connection.prepareStatement("DROP TABLE IF EXISTS "+JdbcLog.EVENT_LOG_TABLE+";").execute(); + + // now try to log an error + try { + log.onIncoming("DB is messed up"); + } catch(OutOfMemoryError err) { + fail("We seem to get an out of memory error b/c of stack overflow b/c we" + + "keep calling jdbc logger recursively in case of misconfiguration: "+err.getMessage()); + } finally { + // put the tables back so they can be cleaned up in tearDown() + initializeTableDefinitions(connection); + } + + + } + private void assertLogData(Connection connection, int rowOffset, SessionID sessionID, String text, String tableName) throws SQLException { Statement s = connection.createStatement(); @@ -82,7 +113,7 @@ s.close(); } - private int getRowCount(Connection connection, String tableName) throws SQLException { + public static int getRowCount(Connection connection, String tableName) throws SQLException { Statement s = connection.createStatement(); ResultSet rs = s.executeQuery("select count(*) from " + tableName); if (rs.next()) { @@ -93,7 +124,7 @@ return 0; } - private void initializeTableDefinitions(Connection connection) throws ConfigError { + public static void initializeTableDefinitions(Connection connection) throws ConfigError { try { JdbcTestSupport.loadSQL(connection, "core/src/main/config/sql/mysql/messages_log_table.sql", new JdbcTestSupport.HypersonicPreprocessor(null)); Index: src/test/java/quickfix/SessionTest.java =================================================================== --- src/test/java/quickfix/SessionTest.java (revision 576) +++ src/test/java/quickfix/SessionTest.java (working copy) @@ -66,6 +66,28 @@ assertEquals(2, state.getNextTargetMsgSeqNum()); } + /** Veifies that the session has been registered before the logger tries accessing it + * Use case: + * JdbcLogger not setup correctly, barfs during Session creation, tries to log and + * can't find the session in global session list yet + */ + public void testSessionRegisteredCorrectly() throws Exception { + SessionSettings settings = SessionSettingsTest.setUpSession(null); + settings.setString(Session.SETTING_USE_DATA_DICTIONARY, "N"); + JdbcTestSupport.setHypersonicSettings(settings); + // do not initialize the SQL tables so that the JdbcLog will fail + SessionID sessionID = new SessionID("FIX.4.2", "SENDER-sessionRegister", "TARGET-sessionRegister"); + settings.setString(sessionID, "ConnectionType", "acceptor"); + DefaultSessionFactory factory = new DefaultSessionFactory(new UnitTestApplication(), new MemoryStoreFactory(), + new JdbcLogFactory(settings)); + try { + Session session = factory.create(sessionID, settings); + assertNotNull(session); + } catch(NullPointerException nex) { + fail("Session not registering correctly so JdbcLog fails while printing an error: "+nex.getMessage()); + } + } + private Session setUpSession(Application application) { SessionID sessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, "SENDER", "TARGET"); Session session = SessionFactoryTestSupport.createSession(sessionID, application, false); Index: src/test/java/quickfix/SessionSettingsTest.java =================================================================== --- src/test/java/quickfix/SessionSettingsTest.java (revision 576) +++ src/test/java/quickfix/SessionSettingsTest.java (working copy) @@ -31,8 +31,6 @@ public class SessionSettingsTest extends TestCase { - private String settingsString; - public SessionSettingsTest(String name) { super(name); } @@ -101,8 +99,8 @@ return setUpSession(null); } - private SessionSettings setUpSession(String extra) throws ConfigError { - settingsString = new String(); + public static SessionSettings setUpSession(String extra) throws ConfigError { + String settingsString = ""; settingsString += "#comment\n"; settingsString += "[DEFAULT]\n"; settingsString += "Empty=\n"; @@ -134,8 +132,7 @@ } ByteArrayInputStream cfg = new ByteArrayInputStream(settingsString.getBytes()); - SessionSettings settings = new SessionSettings(cfg); - return settings; + return new SessionSettings(cfg); } public void testSessionKeyIterator() throws Exception { Index: src/main/java/quickfix/LogBase.java =================================================================== Index: src/main/java/quickfix/Session.java =================================================================== --- src/main/java/quickfix/Session.java (revision 576) +++ src/main/java/quickfix/Session.java (working copy) @@ -268,7 +268,7 @@ state.setHeartBeatInterval(heartbeatInterval); state.setInitiator(heartbeatInterval != 0); state.setMessageStore(messageStoreFactory.create(sessionID)); - + registerSession(this); getLog().onEvent("Session " + sessionID + " schedule is " + sessionSchedule); try { if (!checkSessionTime()) { Index: src/main/java/quickfix/JdbcLog.java =================================================================== --- src/main/java/quickfix/JdbcLog.java (revision 576) +++ src/main/java/quickfix/JdbcLog.java (working copy) @@ -27,10 +27,11 @@ import javax.sql.DataSource; class JdbcLog implements quickfix.Log { - private static final String MESSAGES_LOG_TABLE = "messages_log"; - private static final String EVENT_LOG_TABLE = "event_log"; + public static final String MESSAGES_LOG_TABLE = "messages_log"; + public static final String EVENT_LOG_TABLE = "event_log"; private final SessionID sessionID; private final DataSource dataSource; + private boolean recursiveException = false; public JdbcLog(SessionSettings settings, SessionID sessionID) throws SQLException, ClassNotFoundException, ConfigError, FieldConvertError { @@ -51,9 +52,21 @@ insert(MESSAGES_LOG_TABLE, value); } + /** Protect from the situation when you have recursive calls + * into the logger b/c the previous one failed (in case of a failed DB connection, for example). + * In case of going into a failure mode set a flag, ignore the recursive request and reset the flag. + * @param tableName + * @param value + */ private void insert(String tableName, String value) { Connection connection = null; PreparedStatement insert = null; + if(recursiveException) { + recursiveException = false; + // todo: should we print to stderr here or somewhere else? Need to at least log to something! + return; + } + recursiveException = false; try { connection = dataSource.getConnection(); insert = connection.prepareStatement("INSERT INTO " + tableName @@ -67,6 +80,7 @@ insert.setString(6, value); insert.execute(); } catch (SQLException e) { + recursiveException = true; LogUtil.logThrowable(sessionID, e.getMessage(), e); } finally { JdbcUtil.close(sessionID, insert); Index: src/main/java/quickfix/DefaultSessionFactory.java =================================================================== --- src/main/java/quickfix/DefaultSessionFactory.java (revision 576) +++ src/main/java/quickfix/DefaultSessionFactory.java (working copy) @@ -168,7 +168,6 @@ // accessing the session before it's fully constructed. // - Session.registerSession(session); application.onCreate(sessionID); return session;