/**
*
*/
package edu.harvard.econcs.turkserver.server.mysql;
import edu.harvard.econcs.turkserver.api.HITWorker;
import edu.harvard.econcs.turkserver.schema.Experiment;
import edu.harvard.econcs.turkserver.schema.Quiz;
import edu.harvard.econcs.turkserver.schema.Round;
import edu.harvard.econcs.turkserver.schema.Session;
import edu.harvard.econcs.turkserver.server.ExperimentControllerImpl;
import edu.harvard.econcs.turkserver.server.HITWorkerImpl;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.collect.Multimap;
/**
* A class to keep track of users and HITs. Must be thread safe.
* Can be database-backed or persist in memory.
*
* @author mao
*
*/
public abstract class ExperimentDataTracker {
protected final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName());
public static final int USERNAME_LIMIT = 40;
public static class SessionSummary {
public final int createdHITs;
public final int assignedHITs;
public final int completedHITs;
public final int submittedHITs;
SessionSummary(int createdHITs, int assignedHITs, int completedHITs, int submittedHITs) {
this.createdHITs = createdHITs;
this.assignedHITs = assignedHITs;
this.completedHITs = completedHITs;
this.submittedHITs = submittedHITs;
}
}
/**
* Get all experiments in this set
* @return
*/
public abstract Collection<Experiment> getSetExperiments();
/**
* Get experiment data
* @param experimentId
* @return
*/
public abstract Experiment getExperiment(String experimentId);
/**
* Get all experiments and corresponding sessions
* @return
*/
public abstract Multimap<Experiment, Session> getAllExperimentSessions();
/**
* Get all rounds for an experiment
* @param experimentId
* @return
*/
public abstract List<Round> getExperimentRounds(String experimentId);
/**
* Get all experiments and corresponding rounds
* @return
*/
public abstract Multimap<Experiment, Round> getAllExperimentRounds();
/**
* Get all completed sessions in this set.
* @return
*/
public abstract List<Session> getCompletedSessions();
/**
* Check if we have a record of a sessionID in the database,
* including from other sets
* @param hitID
* @return
*/
public abstract boolean hitExistsInDB(String hitID);
/**
* Get information about currently recorded HITs in the database
* @return
*/
public abstract SessionSummary getSetSessionSummary();
/**
* Get the quiz results in the current set for the worker in question
* @param workerId
* @return
*/
public abstract Collection<Quiz> getSetQuizRecords(String workerId);
/**
* Returns a list of all sessions in the current experiment set
* associated with a worker that have had experiments completed
* @param workerId
* @return
*/
public abstract Collection<Session> getSetSessionInfoForWorker(String workerId);
/**
* Get the assignment Id, if any, for a session
* @param sessionID
* @return
*/
public abstract Session getStoredSessionInfo(String hitId);
/**
* To be implemented method to save the session for a tracker
* @param record
*/
public abstract void saveSession(Session record);
/**
* Adds a hitId to the current set in the database
* @param hitId
*/
public abstract void saveHITId(String hitId);
/**
* Save the results of a quiz
* @param hitId
* @param workerId
* @param qr
*/
public abstract void saveQuizResults(String hitId, String workerId, Quiz qr);
/**
* Associate an assignment and worker Id to a session
* @param hitId
* @param assignmentId
* @param workerId
*/
public void saveWorkerAssignment(HITWorkerImpl session, String assignmentId, String workerId) {
Session record = session.getSessionRecord();
record.setAssignmentId(assignmentId);
record.setWorkerId(workerId);
saveSession(record);
}
/**
* Associate a user name to a session (only for the lobby anyway)
* Can also store the time this happened
* @param session
* @param username
*/
public void saveUsername(HITWorkerImpl session, String username) {
// Update with username and the time they entered the lobby
// TODO worry about previous users for this session's lobby Time
// TODO automatic truncation right now but we should fix this in frontend
if( username.length() > USERNAME_LIMIT ) username = username.substring(0, USERNAME_LIMIT);
Session record = session.getSessionRecord();
record.setUsername(username);
saveSession(record);
}
/**
* Saves the IP address for a given session
* @param session
* @param remoteSocketAddress
*/
public void saveIP(HITWorkerImpl session, InetAddress remoteAddress, Date lobbyTime) {
Session record = session.getSessionRecord();
record.setIpAddr(remoteAddress.getHostAddress());
record.setLobbyTime(new Timestamp(lobbyTime.getTime()));
saveSession(record);
}
/**
* Save a bonus that will be paid to a particular worker
* @param hitWorker
* @param amount
*/
public final void saveBonusAmount(HITWorkerImpl hitWorker, double amount) {
Session record = hitWorker.getSessionRecord();
record.setBonus(BigDecimal.valueOf(amount));
saveSession(record);
}
/**
* Saves the experiment that a session started
* @param session
* @param experimentID
*/
public void saveExperiment(HITWorkerImpl session, String experimentID) {
Session record = session.getSessionRecord();
record.setExperimentId(experimentID);
saveSession(record);
}
/**
* Save the answers in the exit survey
* @param hitId
* @param workerId
* @param comments
*/
public void saveExitSurveyResults(HITWorkerImpl session, String comments) {
Session record = session.getSessionRecord();
record.setComment(comments);
saveSession(record);
}
/**
* Saves an experiment start time in the db
* @param expId
* @param size
* @param inputdata
* @param startTime
*/
protected abstract void saveExpStartTime(String expId, int size, String inputdata, long startTime);
/**
* Initialize a round in the database
* @param expId
* @param currentRound
*/
protected abstract void saveExpRoundStart(String expId, int round, long startTime);
/**
* Save the treatment data for a round, if any
* @param expId
* @param currentRound
* @param inputData
*/
protected abstract void saveExpRoundInput(String expId, int currentRound, String inputData);
/**
* Save the results of a round to the database
* @param expId
* @param round
* @param roundLog
*/
protected abstract void saveExpRoundEnd(String expId, int round, long endTime, String roundLog);
/**
* Saves an experiment end time in the db
* @param expId
* @param endTime
* @param logOutput
*/
protected abstract void saveExpEndInfo(String expId, long endTime, String logOutput);
/**
* Saves the amount of time (percent) a client was inactive over the entire experiment
*
* @param session
*/
protected void saveSessionCompleteInfo(HITWorkerImpl session) {
Session record = session.getSessionRecord();
record.setInactiveData(session.getInactiveInfo());
record.setInactivePercent(session.getInactivePercent());
record.setNumDisconnects(session.getNumDisconnects());
saveSession(record);
}
/**
* Clears the worker for a session, as well as username.
* However, does not clear the assignment,
* which is reset when someone else connects
* @param id
*/
public abstract void clearWorkerForSession(String id);
/**
* Removes a session record from the database
* @param hitId
* @return true if the session was deleted
*/
public abstract boolean deleteSession(String hitId);
/**
* Deletes HIT IDs that are not in experiment or completed from database, and returns them
* @return
*/
public abstract List<Session> expireUnusedSessions();
/**
* Record that an experiment has started with a set of clients
* @param expId
* @param group
* @param startTime
*/
public final void newExperimentStarted(ExperimentControllerImpl expCont) {
String expId = expCont.getExpId();
saveExpStartTime(expId, expCont.getGroup().groupSize(), expCont.getInputData(), expCont.getStartTime());
for( HITWorker session : expCont.getGroup().getHITWorkers() ) {
saveExperiment((HITWorkerImpl) session, expId);
}
}
public void experimentRoundStarted(ExperimentControllerImpl expCont, long startTime) {
saveExpRoundStart(expCont.getExpId(), expCont.getCurrentRound(), startTime);
}
public void saveRoundInput(ExperimentControllerImpl expCont, String inputData) {
saveExpRoundInput(expCont.getExpId(), expCont.getCurrentRound(), inputData);
}
public final void experimentRoundComplete(ExperimentControllerImpl expCont, long endTime, String roundLog) {
saveExpRoundEnd(expCont.getExpId(), expCont.getCurrentRound(), endTime, roundLog);
}
/**
* Record that experiment is finished and log output
* @param expCont
* @param logOutput
*/
public final void experimentFinished(ExperimentControllerImpl expCont, String logOutput) {
String expId = expCont.getExpId();
// Doing DB commits first so there is no limbo state
saveExpEndInfo(expId, expCont.getFinishTime(), logOutput);
// store final client info, with inactive time measured properly for disconnections
for( HITWorker session : expCont.getGroup().getHITWorkers() ) {
HITWorkerImpl hitw = (HITWorkerImpl) session;
hitw.finalizeActivity();
saveSessionCompleteInfo(hitw);
}
}
}