/******************************************************************************* * 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 java.io.IOException; import java.util.Collection; import java.util.Date; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * Used by the session communications code. Not intended to be used by applications. All dynamic data is protected by * the session's intrinsic lock. The log and message store implementation must be thread safe. */ public final class SessionState { private final Object lock; private final Log log; // MessageStore implementation must be thread safe private final MessageStore messageStore; private final Lock senderMsgSeqNumLock = new ReentrantLock(); private final Lock targetMsgSeqNumLock = new ReentrantLock(); private final boolean initiator; private long logonTimeoutMs = 10000L; private long logoutTimeoutMs = 2000L; private boolean logonSent; private boolean logonReceived; private boolean logoutSent; private boolean logoutReceived = false; private int testRequestCounter; private long lastSentTime; private long lastReceivedTime; private double testRequestDelayMultiplier; private long heartBeatMillis = Long.MAX_VALUE; private int heartBeatInterval; /** * The resend range when sending a resend request. * If a gap is detected and messages from x to y are needed, the received messages are checked against the values x and y that are stored in the resendRange. * Some FIX Engine do not support resendRequest range greater than a given value. There, in this case, the ResendRequest have to be splitted. * E.g.: CME will reject any resend request for more than 2500 messages * The solution is to send resend request with smaller range until the global range has been requested. * * the resendRange contains 3 values: * <ol> * <li>the begin index of the global resend request</li> * <li>the last index of the global resend request</li> * <li>the actual last index of the splitted sub resend request</li> * </ol> */ private int[] resendRange = new int[] { 0, 0, 0 }; private boolean resetSent; private boolean resetReceived; private String logoutReason; /* * If this is anything other than zero it's the value of the 789/NextExpectedMsgSeqNum tag in the last Logon message sent. * It is used to determine if the recipient has enough information (assuming they support 789) to avoid the need * for a resend request i.e. they should be resending any necessary missing messages already. This value is used * to populate the resendRange if necessary. */ private final AtomicInteger nextExpectedMsgSeqNum = new AtomicInteger(0); // The messageQueue should be accessed from a single thread private final Map<Integer, Message> messageQueue = new LinkedHashMap<Integer, Message>(); public SessionState(Object lock, Log log, int heartBeatInterval, boolean initiator, MessageStore messageStore, double testRequestDelayMultiplier) { this.lock = lock; this.initiator = initiator; this.messageStore = messageStore; setHeartBeatInterval(heartBeatInterval); this.log = log == null ? new NullLog() : log; this.testRequestDelayMultiplier = testRequestDelayMultiplier; } public int getHeartBeatInterval() { synchronized (lock) { return heartBeatInterval; } } public void setHeartBeatInterval(int heartBeatInterval) { synchronized (lock) { this.heartBeatInterval = heartBeatInterval; } setHeartBeatMillis(heartBeatInterval * 1000L); } private void setHeartBeatMillis(long heartBeatMillis) { synchronized (lock) { this.heartBeatMillis = heartBeatMillis; } } long getHeartBeatMillis() { synchronized (lock) { return heartBeatMillis; } } public boolean isHeartBeatNeeded() { long millisSinceLastSentTime = SystemTime.currentTimeMillis() - getLastSentTime(); return millisSinceLastSentTime >= getHeartBeatMillis() && getTestRequestCounter() == 0; } public boolean isInitiator() { return initiator; } public long getLastReceivedTime() { synchronized (lock) { return lastReceivedTime; } } public void setLastReceivedTime(long lastReceivedTime) { synchronized (lock) { this.lastReceivedTime = lastReceivedTime; } } public long getLastSentTime() { synchronized (lock) { return lastSentTime; } } public void setLastSentTime(long lastSentTime) { synchronized (lock) { this.lastSentTime = lastSentTime; } } public Log getLog() { return log; } public boolean isLogonAlreadySent() { return isInitiator() && isLogonSent(); } public boolean isLogonReceived() { synchronized (lock) { return logonReceived; } } public void setLogonReceived(boolean logonReceived) { synchronized (lock) { this.logonReceived = logonReceived; } } public boolean isLogonSendNeeded() { return isInitiator() && !isLogonSent(); } public boolean isLogonSent() { synchronized (lock) { return logonSent; } } public void setLogonSent(boolean logonSent) { synchronized (lock) { this.logonSent = logonSent; } } public boolean isLogonTimedOut() { synchronized (lock) { return isLogonSent() && SystemTime.currentTimeMillis() - getLastReceivedTime() >= getLogonTimeoutMs(); } } public void setLogonTimeout(int logonTimeout) { setLogonTimeoutMs(logonTimeout * 1000L); } public int getLogonTimeout() { return (int) (getLogonTimeoutMs() / 1000L); } public void setLogoutTimeout(int logoutTimeout) { setLogoutTimeoutMs(logoutTimeout * 1000L); } public int getLogoutTimeout() { return (int) (getLogoutTimeoutMs() / 1000L); } private void setLogoutTimeoutMs(long logoutTimeoutMs) { synchronized (lock) { this.logoutTimeoutMs = logoutTimeoutMs; } } private long getLogoutTimeoutMs() { synchronized (lock) { return logoutTimeoutMs; } } private void setLogonTimeoutMs(long logonTimeoutMs) { synchronized (lock) { this.logonTimeoutMs = logonTimeoutMs; } } private long getLogonTimeoutMs() { synchronized (lock) { return logonTimeoutMs; } } public boolean isLogoutSent() { synchronized (lock) { return logoutSent; } } public void setLogoutSent(boolean logoutSent) { synchronized (lock) { this.logoutSent = logoutSent; } } public boolean isLogoutReceived() { synchronized (lock) { return logoutReceived; } } public void setLogoutReceived(boolean logoutReceived) { synchronized (lock) { this.logoutReceived = logoutReceived; } } public boolean isLogoutTimedOut() { return isLogoutSent() && ((SystemTime.currentTimeMillis() - getLastSentTime()) >= getLogoutTimeoutMs()); } public MessageStore getMessageStore() { return messageStore; } private int getTestRequestCounter() { synchronized (lock) { return testRequestCounter; } } public double getTestRequestDelayMultiplier() { return testRequestDelayMultiplier; } public void clearTestRequestCounter() { synchronized (lock) { testRequestCounter = 0; } } public void incrementTestRequestCounter() { synchronized (lock) { testRequestCounter++; } } public boolean isTestRequestNeeded() { long millisSinceLastReceivedTime = timeSinceLastReceivedMessage(); return millisSinceLastReceivedTime >= ((1 + testRequestDelayMultiplier) * (getTestRequestCounter() + 1)) * getHeartBeatMillis(); } private long timeSinceLastReceivedMessage() { return SystemTime.currentTimeMillis() - getLastReceivedTime(); } public boolean isTimedOut() { long millisSinceLastReceivedTime = timeSinceLastReceivedMessage(); return millisSinceLastReceivedTime >= 2.4 * getHeartBeatMillis(); } public boolean set(int sequence, String message) throws IOException { return messageStore.set(sequence, message); } public void get(int first, int last, Collection<String> messages) throws IOException { messageStore.get(first, last, messages); } public void enqueue(int sequence, Message message) { messageQueue.put(sequence, message); } public Message dequeue(int sequence) { return messageQueue.remove(sequence); } public Message getNextQueuedMessage() { return messageQueue.size() > 0 ? messageQueue.values().iterator().next() : null; } public Collection<Integer> getQueuedSeqNums() { return messageQueue.keySet(); } public void clearQueue() { messageQueue.clear(); } public void lockSenderMsgSeqNum() { senderMsgSeqNumLock.lock(); } public void unlockSenderMsgSeqNum() { senderMsgSeqNumLock.unlock(); } public void lockTargetMsgSeqNum() { targetMsgSeqNumLock.lock(); } public void unlockTargetMsgSeqNum() { targetMsgSeqNumLock.unlock(); } public int getNextSenderMsgSeqNum() throws IOException { return messageStore.getNextSenderMsgSeqNum(); } public int getNextTargetMsgSeqNum() throws IOException { return messageStore.getNextTargetMsgSeqNum(); } public void setNextTargetMsgSeqNum(int sequence) throws IOException { messageStore.setNextTargetMsgSeqNum(sequence); } public void incrNextSenderMsgSeqNum() throws IOException { messageStore.incrNextSenderMsgSeqNum(); } public void incrNextTargetMsgSeqNum() throws IOException { messageStore.incrNextTargetMsgSeqNum(); } public Date getCreationTime() throws IOException { return messageStore.getCreationTime(); } public void reset() { try { messageStore.reset(); } catch (IOException e) { throw new RuntimeError(e); } } public void setResendRange(int low, int high) { synchronized (lock) { resendRange[0] = low; resendRange[1] = high; } } public void setResendRange(int low, int high, int currentResend) { synchronized (lock) { resendRange[0] = low; resendRange[1] = high; resendRange[2] = currentResend; } } public boolean isResendRequested() { synchronized (lock) { return !(resendRange[0] == 0 && resendRange[1] == 0); } } public int[] getResendRange() { synchronized (lock) { return resendRange; } } public boolean isResetReceived() { synchronized (lock) { return resetReceived; } } public void setResetReceived(boolean resetReceived) { synchronized (lock) { this.resetReceived = resetReceived; } } public boolean isResetSent() { synchronized (lock) { return resetSent; } } public void setResetSent(boolean resetSent) { synchronized (lock) { this.resetSent = resetSent; } } /** * No actual resend request has occurred but at logon we populated tag 789 so that the other side knows we * are missing messages without an explicit resend request and should immediately reply with the missing * messages. * * This is expected to be called only in the scenario where target is too high on logon and tag 789 is supported. */ public void setResetRangeFromLastExpectedLogonNextSeqNumLogon() { synchronized (lock) { // we have already requested all msgs from nextExpectedMsgSeqNum to infinity setResendRange(getLastExpectedLogonNextSeqNum(), 0); // clean up the variable (not really needed) setLastExpectedLogonNextSeqNum(0); } } /** * @param nextExpectedMsgSeqNum * * This method is thread safe (atomic set). */ public void setLastExpectedLogonNextSeqNum(int lastExpectedLogonNextSeqNum) { this.nextExpectedMsgSeqNum.set(lastExpectedLogonNextSeqNum); } /** * @return nextExpectedMsgSeqNum * * This method is thread safe (atomic get). */ public int getLastExpectedLogonNextSeqNum() { return this.nextExpectedMsgSeqNum.get(); } /** * @return true if we populated tag 789 at logon and our sequence * numbers don't line up we are in an implicit resend mode. * * This method is thread safe (atomic get). */ public boolean isExpectedLogonNextSeqNumSent() { return this.nextExpectedMsgSeqNum.get() != 0; } public void setLogoutReason(String reason) { synchronized (lock) { logoutReason = reason; } } public String getLogoutReason() { synchronized (lock) { return logoutReason; } } public void clearLogoutReason() { synchronized (lock) { logoutReason = ""; } } public Object getLock() { return lock; } private final static class NullLog implements Log { public void onOutgoing(String message) { } public void onIncoming(String message) { } public void onEvent(String text) { } public void onErrorEvent(String text) { } public void clear() { } } }