package org.f1x.v1.state;
import org.f1x.util.TimeSource;
import org.gflogger.GFLog;
import org.gflogger.GFLogFactory;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* Session Store backed by file (updated periodically or at the end of each connection)
*/
public class SimpleFileSessionStore extends MemorySessionState {
private static final GFLog LOGGER = GFLogFactory.getLog(SimpleFileSessionStore.class);
private final RandomAccessFile raf;
private final Flusher flusher;
public SimpleFileSessionStore (File file) throws LostSessionStateException {
this(file, null, 0, false);
}
/**
* @param file file to hold session state
* @param flushPeriod How often session should flush the changes (in milliseconds). Zero or negative value disables periodic flush (in which case state is flushed on disconnect).
*/
public SimpleFileSessionStore (File file, TimeSource timeSource, int flushPeriod, boolean truncate) throws LostSessionStateException {
final boolean existingFile = file.exists();
try {
raf = new RandomAccessFile(file, "rw");
if (existingFile && ! truncate) {
long lastConnTimestamp = raf.readLong();
int senderCompId = raf.readInt();
int targetCompId = raf.readInt();
if (senderCompId < 1 || targetCompId < 1)
throw new RuntimeException("Invalid session state loaded");
boolean isCommitted = raf.readBoolean();
if ( ! isCommitted)
throw new LostSessionStateException();
setLastConnectionTimestamp(lastConnTimestamp);
setNextSenderSeqNum(senderCompId);
setNextTargetSeqNum(targetCompId);
}
store(false); // Mark as incomplete
} catch (IOException e) {
throw new RuntimeException(e);
}
flusher = (flushPeriod > 0) ? new Flusher(file, timeSource, flushPeriod) : null;
}
private void store (boolean isComplete) throws IOException {
raf.seek(0);
raf.writeLong(getLastConnectionTimestamp());
raf.writeInt(getNextSenderSeqNum());
raf.writeInt(getNextTargetSeqNum());
raf.writeBoolean(isComplete);
raf.getFD().sync();
}
@Override
public void flush() {
try {
store(true);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// @Override //TODO
public void close() {
flusher.interrupt();
flush();
}
protected class Flusher extends Thread { //TODO: Replace by alloc-free version of ScheduledExecutorService ?
private final TimeSource timeSource;
protected final int flushPeriod;
protected Flusher(File file, TimeSource timeSource, int flushPeriod) {
super("State flusher for " + file.getName());
setDaemon(true);
setPriority(Thread.NORM_PRIORITY - 1);
this.timeSource = timeSource;
this.flushPeriod = flushPeriod;
}
@Override
public void run () {
while (true) {
try {
timeSource.sleep(flushPeriod);
store (false);
} catch (InterruptedException e) {
break;
} catch (Throwable e) {
LOGGER.error().append("Error writing FIX log").append(e).commit();
}
}
}
}
/**
* Error that signals that session state stored on disk is potentially not up-to-date (system crashed?).
* Clients should report this problem to user and reset session state.
*/
public static class LostSessionStateException extends Exception {
LostSessionStateException () {
super ("Session State was lost (sequence reset is required)");
}
}
}