/******************************************************************************* * Copyright (c) quickfixengine.org All rights reserved. * * This file is part of the QuickFIX FIX Engine * * This file may be distributed under the terms of the quickfixengine.org * license as defined by quickfixengine.org and appearing in the file * LICENSE included in the packaging of this file. * * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE. * * See http://www.quickfixengine.org/LICENSE for licensing information. * * Contact ask@quickfixengine.org if any conditions of this licensing * are not clear to you. ******************************************************************************/ package quickfix; import com.github.lburgazzoli.quickfixj.core.IFIXContext; import javax.sql.DataSource; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.TimeZone; import static quickfix.JdbcSetting.SETTING_JDBC_SESSION_ID_DEFAULT_PROPERTY_VALUE; import static quickfix.JdbcSetting.SETTING_JDBC_STORE_MESSAGES_TABLE_NAME; import static quickfix.JdbcSetting.SETTING_JDBC_STORE_SESSIONS_TABLE_NAME; class JdbcStore implements MessageStore { private final static String DEFAULT_SESSION_TABLE_NAME = "sessions"; private final static String DEFAULT_MESSAGE_TABLE_NAME = "messages"; private final MemoryStore cache; private final IFIXContext context; private final boolean extendedSessionIdSupported; private final DataSource dataSource; private final SessionID sessionID; private final String sessionTableName; private final String messageTableName; private final String defaultSessionIdPropertyValue; private String SQL_UPDATE_SEQNUMS; private String SQL_INSERT_SESSION; private String SQL_GET_SEQNUMS; private String SQL_UPDATE_MESSAGE; private String SQL_INSERT_MESSAGE; private String SQL_GET_MESSAGES; private String SQL_UPDATE_SESSION; private String SQL_DELETE_MESSAGES; public JdbcStore(IFIXContext context,SessionSettings settings, SessionID sessionID, DataSource ds) throws Exception { this.sessionID = sessionID; this.context = context; this.cache = new MemoryStore(context); if (settings.isSetting(SETTING_JDBC_STORE_SESSIONS_TABLE_NAME)) { sessionTableName = settings .getString(SETTING_JDBC_STORE_SESSIONS_TABLE_NAME); } else { sessionTableName = DEFAULT_SESSION_TABLE_NAME; } if (settings.isSetting(SETTING_JDBC_STORE_MESSAGES_TABLE_NAME)) { messageTableName = settings.getString(SETTING_JDBC_STORE_MESSAGES_TABLE_NAME); } else { messageTableName = DEFAULT_MESSAGE_TABLE_NAME; } if (settings.isSetting(SETTING_JDBC_SESSION_ID_DEFAULT_PROPERTY_VALUE)) { defaultSessionIdPropertyValue = settings.getString( SETTING_JDBC_SESSION_ID_DEFAULT_PROPERTY_VALUE); } else { defaultSessionIdPropertyValue = SessionID.NOT_SET; } dataSource = ds == null ? JdbcUtil.getDataSource(settings, sessionID) : ds; // One table is sampled for the extended session ID columns. Be sure // that all tables are extended if you extend any of them. extendedSessionIdSupported = JdbcUtil.determineSessionIdSupport(dataSource, sessionTableName); setSqlStrings(); loadCache(); } private final void setSqlStrings() { String idWhereClause = JdbcUtil.getIDWhereClause(extendedSessionIdSupported); String idColumns = JdbcUtil.getIDColumns(extendedSessionIdSupported); String idPlaceholders = JdbcUtil.getIDPlaceholders(extendedSessionIdSupported); SQL_UPDATE_SEQNUMS = "UPDATE " + sessionTableName + " SET incoming_seqnum=?, " + "outgoing_seqnum=? WHERE " + idWhereClause; SQL_INSERT_SESSION = "INSERT INTO " + sessionTableName + " (" + idColumns + ", creation_time,incoming_seqnum, outgoing_seqnum) VALUES (" + idPlaceholders + ",?,?,?)"; SQL_GET_SEQNUMS = "SELECT creation_time, incoming_seqnum, outgoing_seqnum FROM " + sessionTableName + " WHERE " + idWhereClause; SQL_UPDATE_MESSAGE = "UPDATE " + messageTableName + " SET message=? " + "WHERE " + idWhereClause + " and msgseqnum=?"; SQL_INSERT_MESSAGE = "INSERT INTO " + messageTableName + " (" + idColumns + ", msgseqnum,message) VALUES (" + idPlaceholders + ",?,?)"; SQL_GET_MESSAGES = "SELECT message FROM " + messageTableName + " WHERE " + idWhereClause + " and msgseqnum>=? and msgseqnum<=? " + "ORDER BY msgseqnum"; SQL_UPDATE_SESSION = "UPDATE " + sessionTableName + " SET creation_time=?, " + "incoming_seqnum=?, outgoing_seqnum=? " + "WHERE " + idWhereClause; SQL_DELETE_MESSAGES = "DELETE FROM " + messageTableName + " WHERE " + idWhereClause; } private void loadCache() throws SQLException, IOException { Connection connection = null; PreparedStatement query = null; PreparedStatement insert = null; ResultSet rs = null; try { connection = dataSource.getConnection(); query = connection.prepareStatement(SQL_GET_SEQNUMS); setSessionIdParameters(query, 1); rs = query.executeQuery(); if (rs.next()) { cache.setCreationTime(SystemTime.getUtcCalendar(rs.getTimestamp(1))); cache.setNextTargetMsgSeqNum(rs.getInt(2)); cache.setNextSenderMsgSeqNum(rs.getInt(3)); } else { insert = connection.prepareStatement(SQL_INSERT_SESSION); int offset = setSessionIdParameters(insert, 1); insert.setTimestamp(offset++, new Timestamp(cache.getCreationTime().getTime())); insert.setInt(offset++, cache.getNextTargetMsgSeqNum()); insert.setInt(offset, cache.getNextSenderMsgSeqNum()); insert.execute(); } } finally { JdbcUtil.close(context,sessionID, rs); JdbcUtil.close(context,sessionID, query); JdbcUtil.close(context,sessionID, insert); JdbcUtil.close(context,sessionID, connection); } } private int setSessionIdParameters(PreparedStatement query, int offset) throws SQLException { return JdbcUtil.setSessionIdParameters(sessionID, query, offset, extendedSessionIdSupported, defaultSessionIdPropertyValue); } public Date getCreationTime() throws IOException { return cache.getCreationTime(); } public int getNextSenderMsgSeqNum() throws IOException { return cache.getNextSenderMsgSeqNum(); } public int getNextTargetMsgSeqNum() throws IOException { return cache.getNextTargetMsgSeqNum(); } public void incrNextSenderMsgSeqNum() throws IOException { cache.incrNextSenderMsgSeqNum(); setNextSenderMsgSeqNum(cache.getNextSenderMsgSeqNum()); } public void incrNextTargetMsgSeqNum() throws IOException { cache.incrNextTargetMsgSeqNum(); setNextTargetMsgSeqNum(cache.getNextTargetMsgSeqNum()); } public void reset() throws IOException { cache.reset(); Connection connection = null; PreparedStatement deleteMessages = null; PreparedStatement updateTime = null; try { connection = dataSource.getConnection(); deleteMessages = connection.prepareStatement(SQL_DELETE_MESSAGES); setSessionIdParameters(deleteMessages, 1); deleteMessages.execute(); updateTime = connection.prepareStatement(SQL_UPDATE_SESSION); updateTime.setTimestamp(1, new Timestamp(Calendar.getInstance( TimeZone.getTimeZone("UTC")).getTimeInMillis())); updateTime.setInt(2, getNextTargetMsgSeqNum()); updateTime.setInt(3, getNextSenderMsgSeqNum()); setSessionIdParameters(updateTime, 4); updateTime.execute(); } catch (SQLException e) { throw (IOException) new IOException(e.getMessage()).initCause(e); } catch (IOException e) { throw e; } finally { JdbcUtil.close(context,sessionID, deleteMessages); JdbcUtil.close(context,sessionID, updateTime); JdbcUtil.close(context,sessionID, connection); } } public void get(int startSequence, int endSequence, Collection<String> messages) throws IOException { Connection connection = null; PreparedStatement query = null; ResultSet rs = null; try { connection = dataSource.getConnection(); query = connection.prepareStatement(SQL_GET_MESSAGES); int offset = setSessionIdParameters(query, 1); query.setInt(offset++, startSequence); query.setInt(offset, endSequence); rs = query.executeQuery(); while (rs.next()) { String message = rs.getString(1); messages.add(message); } } catch (SQLException e) { throw (IOException) new IOException(e.getMessage()).initCause(e); } finally { JdbcUtil.close(context,sessionID, rs); JdbcUtil.close(context,sessionID, query); JdbcUtil.close(context,sessionID, connection); } } public boolean set(int sequence, String message) throws IOException { Connection connection = null; PreparedStatement insert = null; ResultSet rs = null; try { connection = dataSource.getConnection(); insert = connection.prepareStatement(SQL_INSERT_MESSAGE); int offset = setSessionIdParameters(insert, 1); insert.setInt(offset++, sequence); insert.setString(offset, message); insert.execute(); } catch (SQLException ex) { if (connection != null) { PreparedStatement update = null; try { update = connection.prepareStatement(SQL_UPDATE_MESSAGE); update.setString(1, message); int offset = setSessionIdParameters(update, 2); update.setInt(offset, sequence); boolean status = update.execute(); return !status ? update.getUpdateCount() > 0 : false; } catch (SQLException e) { throw (IOException) new IOException(e.getMessage()).initCause(e); } finally { JdbcUtil.close(context,sessionID, update); } } } finally { JdbcUtil.close(context,sessionID, rs); JdbcUtil.close(context,sessionID, insert); JdbcUtil.close(context,sessionID, connection); } return true; } public void setNextSenderMsgSeqNum(int next) throws IOException { cache.setNextSenderMsgSeqNum(next); storeSequenceNumbers(); } public void setNextTargetMsgSeqNum(int next) throws IOException { cache.setNextTargetMsgSeqNum(next); storeSequenceNumbers(); } private void storeSequenceNumbers() throws IOException { Connection connection = null; PreparedStatement update = null; try { connection = dataSource.getConnection(); update = connection.prepareStatement(SQL_UPDATE_SEQNUMS); update.setInt(1, cache.getNextTargetMsgSeqNum()); update.setInt(2, cache.getNextSenderMsgSeqNum()); setSessionIdParameters(update, 3); update.execute(); } catch (SQLException e) { throw (IOException) new IOException(e.getMessage()).initCause(e); } finally { JdbcUtil.close(context,sessionID, update); JdbcUtil.close(context,sessionID, connection); } } public void refresh() throws IOException { try { loadCache(); } catch (SQLException e) { throw (IOException) new IOException(e.getMessage()).initCause(e); } } DataSource getDataSource() { return dataSource; } }