package geniusweb.simplerunner;
import java.util.List;
import java.util.logging.Level;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import geniusweb.events.ProtocolEvent;
import geniusweb.protocol.CurrentNegoState;
import geniusweb.protocol.NegoProtocol;
import geniusweb.protocol.NegoSettings;
import geniusweb.protocol.NegoState;
import geniusweb.protocol.partyconnection.ProtocolToPartyConnFactory;
import tudelft.utilities.logging.Reporter;
/**
* A simple tool to run a negotiation stand-alone, without starting the servers.
* All referred files and classes need to be stored locally (or be in the
* dependency list if you use maven).
*
* IMPORTANT SimpleRunner has a number of restrictions, compared to a
* run using a runserver and partyserver
*
* - With stand-alone runner, your parties are run together in a single
* classloader. The main implication is that there may arise version conflicts
* between parties.
*
- Stand-alone runner does NOT enforce the time deadline. Parties may
* continue running indefinitely and thus bog down the JVM and stalling
* tournaments.
*
*/
public class Runner implements Runnable {
private final static ObjectMapper jackson = new ObjectMapper();
private final NegoSettings settings;
private final NegoProtocol protocol;
private final ClassPathConnectionFactory connectionfactory;
protected final Reporter log;
private boolean properlyStopped = false;
private final int LOOPTIME = 200;// ms
private final int FINALWAITTIME = 5000;// ms
private long maxruntime;
/**
*
* @param settings the {@link NegoSettings}
* @param connectionfactory the {@link ProtocolToPartyConnFactory}
* @param logger the {@link Reporter} to log problems
* @param maxruntime limit in millisecs. Ignored if 0
*/
public Runner(NegoSettings settings,
ClassPathConnectionFactory connectionfactory, Reporter logger,
long maxruntime) {
if (settings == null || connectionfactory == null) {
throw new NullPointerException("Arguments must be not null");
}
this.settings = settings;
this.log = logger;
this.protocol = settings.getProtocol(log);
this.connectionfactory = connectionfactory;
this.maxruntime = maxruntime;
}
/**
*
* @return true if the runner has finished
*/
public boolean isProperlyStopped() {
return properlyStopped;
}
@Override
public void run() {
protocol.addListener(evt -> handle(evt));
protocol.start(connectionfactory);
long remainingtime = maxruntime;
while (!properlyStopped && (maxruntime == 0 || remainingtime > 0)) {
try {
Thread.sleep(LOOPTIME);
remainingtime -= LOOPTIME;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.log(Level.INFO, "Waiting for connection closure");
remainingtime = FINALWAITTIME;
while (remainingtime > 0
&& !connectionfactory.getOpenConnections().isEmpty()) {
try {
Thread.sleep(LOOPTIME);
remainingtime -= LOOPTIME;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
List openconn = connectionfactory.getOpenConnections();
if (!openconn.isEmpty())
log.log(Level.WARNING, "Connections " + openconn
+ " did not close properly at end of run");
log.log(Level.INFO, "end run");
}
private void handle(ProtocolEvent evt) {
if (evt instanceof CurrentNegoState && ((CurrentNegoState) evt)
.getState().isFinal(System.currentTimeMillis())) {
stop();
}
}
protected void stop() {
logFinal(Level.INFO, protocol.getState());
properlyStopped = true;
}
/**
* Separate so that we can intercept this when mocking, as this will crash
* on mocks because {@link #jackson} can not handle mocks.
*
* @param level the log {@link Level}
* @param state the {@link NegoState} to log
*/
protected void logFinal(Level level, NegoState state) {
try {
log.log(level, "protocol ended normally: "
+ jackson.writeValueAsString(protocol.getState()));
} catch (JsonProcessingException e) {
e.printStackTrace();
}
}
/**
* @return protocol that runs/ran the session.
*/
public NegoProtocol getProtocol() {
return protocol;
}
}