Java tutorial
/* * Copyright (C) 2005-2006, <a href="http://www.ssel.caltech.edu">SSEL</a> * <a href="http://www.cassel.ucla.edu">CASSEL</a>, Caltech/UCLA * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. * * Project Authors: Raj Advani, Walter M. Yuan, and Peter Bossaerts * Email: jmarkets@ssel.caltech.edu */ /* * AuthServ.java * * Created on March 16, 2004, 7:40 PM */ package edu.caltechUcla.sselCassel.projects.jMarkets.server.control; import java.sql.SQLException; import java.util.Enumeration; import java.util.Hashtable; import java.util.Properties; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import edu.caltechUcla.sselCassel.projects.jMarkets.server.data.DBConnector; import edu.caltechUcla.sselCassel.projects.jMarkets.server.data.DBWriter; import edu.caltechUcla.sselCassel.projects.jMarkets.server.data.SessionState; import edu.caltechUcla.sselCassel.projects.jMarkets.server.network.MonitorTransmitter; import edu.caltechUcla.sselCassel.projects.jMarkets.server.updates.AuthUpdate; import edu.caltechUcla.sselCassel.projects.jMarkets.server.updates.NewPeriodUpdate; import edu.caltechUcla.sselCassel.projects.jMarkets.server.updates.PayoffUpdate; import edu.caltechUcla.sselCassel.projects.jMarkets.shared.JMConstants; import edu.caltechUcla.sselCassel.projects.jMarkets.shared.data.SessionIdentifier; import edu.caltechUcla.sselCassel.projects.jMarkets.shared.data.Trader; import edu.caltechUcla.sselCassel.projects.jMarkets.shared.data.def.GroupDef; import edu.caltechUcla.sselCassel.projects.jMarkets.shared.data.def.MarketDef; import edu.caltechUcla.sselCassel.projects.jMarkets.shared.data.def.PeriodDef; import edu.caltechUcla.sselCassel.projects.jMarkets.shared.data.def.SessionDef; import edu.caltechUcla.sselCassel.projects.jMarkets.shared.data.def.SubjectDef; import edu.caltechUcla.sselCassel.projects.jMarkets.shared.data.model.earningstable.EarningsInfo; import edu.caltechUcla.sselCassel.projects.jMarkets.shared.data.model.earningstable.EarningsRow; import edu.caltechUcla.sselCassel.projects.jMarkets.shared.functions.PayoffFunction; /** * * @author Raj Advani, Walter M. Yuan */ public class ControlServ implements Controller { public ControlServ(Properties props) { DBConnector dbc = new DBConnector(props); dbw = new DBWriter(dbc); sessionStates = new Hashtable(); } /** Get the Database writer used by the ControlServ. This is needed so that the * dispatcher can pass this writer to the TradeServ */ public DBWriter getDBWriter() { return dbw; } /** Start a new session. Create a new SessionState object and write the session to the database, * returning the session ID. Store the session in the sessions hashtable so it can be accessed * by its ID when needed */ public synchronized int startNewSession(String name, int numClients, int updateTime, SessionDef sessionInfo) { SessionState sessionState = createSession(name, numClients, updateTime, sessionInfo); int sessionId = dbw.writeSession(name, numClients, sessionInfo); sessionStates.put(new Integer(sessionId), sessionState); return sessionId; } /** Get the array of database Id numbers associated with the given session. These numbers * are invalid unless the game is running. Therefore return null if the session is still * initializing (first period has not yet begun) */ public int[] getSessionClients(int sessionId) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error( "Cannot retrieve recipient list for session " + sessionId + " -- that session does not exist!"); return null; } int state = session.getState(); if (state == SessionState.ACCEPTING_CLIENTS_STATE || state == SessionState.ADMIN_CONNECTING_STATE || state == SessionState.CLIENTS_FULL_STATE) return null; else return session.getAllDatabaseIds(); } /** Create a new session for the given number of clients. Return the SessionState object created, * which contains all the session information */ private SessionState createSession(String name, int numClients, int updateTime, SessionDef sessionInfo) { SessionState sessionState = new SessionState(name, numClients, updateTime, sessionInfo); return sessionState; } /** Return an array of SessionIdentifier objects that contain the ID numbers, names, and status * of all active sessions. Synchronized so that new sessions are not created while * retrieving identifiers */ public synchronized SessionIdentifier[] getSessionIdentifiers() { SessionIdentifier[] identifiers = new SessionIdentifier[sessionStates.size()]; Enumeration sessions = sessionStates.keys(); int index = 0; while (sessions.hasMoreElements()) { Integer sessionId = (Integer) sessions.nextElement(); SessionState sessionState = (SessionState) sessionStates.get(sessionId); String name = sessionState.getName(); int id = sessionId.intValue(); int numClients = sessionState.getNumClients(); int state = sessionState.getState(); String status = "Experiment in progress"; if (state == SessionState.ACCEPTING_CLIENTS_STATE) status = "Accepting new clients"; if (state == SessionState.ADMIN_CONNECTING_STATE) status = "Waiting for admin"; identifiers[index] = new SessionIdentifier(id, name, status, numClients); index++; } return identifiers; } /** Return an array of active session ID numbers */ public int[] getSessionIds() { int[] ids = new int[sessionStates.size()]; Enumeration sessions = sessionStates.keys(); int index = 0; while (sessions.hasMoreElements()) { ids[index] = ((Integer) sessions.nextElement()).intValue(); index++; } return ids; } /** Register the given ExpMonitor (MonitorTransmitter interface) with the given session ID. * Return true if this is the first ExpMonitor link established for this session. Otherwise * return false. The state will be set to ACCEPTING_CLIENTS_STATE once at least one ExpMonitor * has been registered. For ExpMonitors that register after the initial one, the dispatcher will * instruct the MonitorServ to send them the price chart, client connection status, and button status * information */ public boolean registerExpMonitor(int sessionId, MonitorTransmitter ui) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session.getState() == SessionState.ADMIN_CONNECTING_STATE) { session.setState(SessionState.ACCEPTING_CLIENTS_STATE); log.info("Session " + sessionId + " has established initial ExpMonitor link -- awaiting client connections"); log.info("After the session begins it is safe to shut down this monitor"); return true; } return false; } /** Authenticate the given client into the given session. First check to see if the session * is in a state where it can accept new clients. If it is, add the client to the session * and return an AuthUpdate to the DispatchServ so that it can send out a confirmation to * the client. Otherwise return an AuthUpdate with an error message. The disconnected Hashtable * is sent by the DispatchServ and tell us what clients are currently disconnected from the * the server. This is needed so that we do not reauthenticate a client who is already connected */ public AuthUpdate authClient(int sessionId, String name, int dbId, Hashtable disconnected) { try { log.debug("ControlServ has received an authentication request for client " + dbId + " in session " + sessionId); SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot authenticate client for session " + sessionId + " -- that session is inactive!"); return createAuthFailedPacket("You cannot connect to an inactive session", false); } int state = session.getState(); if (state == SessionState.CLIENTS_FULL_STATE) { log.info("Rejected a client authentication attempt: no more client slots available, all connected"); return createAuthFailedPacket("No more players are needed for the current game", false); } else if (state == SessionState.ADMIN_CONNECTING_STATE) { log.info( "Rejecting a client authentication attempt: admin connection not yet established for current game"); return createAuthFailedPacket("Game is not yet ready; please retry in a few seconds", true); } else if (state == SessionState.SHUTDOWN_STATE) { log.info( "Rejected a client authentication attempt: there is no game currently running on this server"); return createAuthFailedPacket("No game is currently running on this server", false); } else if (state == SessionState.GAME_RUNNING_STATE) { return reauthClient(sessionId, session, name, dbId, disconnected); } else if (state == SessionState.ACCEPTING_CLIENTS_STATE) { return authClient(sessionId, session, name, dbId); } } catch (Exception e) { log.error("Failed to process an authentication request in the ControlServ", e); } return createAuthFailedPacket("Authentication failed for unknown reason", false); } /** Authenticate the given client into the given session. Collect the authentication * information into an AuthUpdate object, which will be returned to the dispatcher */ private AuthUpdate authClient(int sessionId, SessionState session, String name, int dbId) { try { int id = session.getNumConnected(); boolean connectSuccess = session.addClient(id, dbId, name); if (!connectSuccess) { log.info("Client " + name + " with database id " + dbId + " is attempting to connect multiple times -- rejecting"); return createAuthFailedPacket("Authentication failed -- you may not connect multiple times", false); } AuthUpdate authPacket = new AuthUpdate(AuthUpdate.AUTH_SUCCESS); authPacket.setUpdateTime(session.getUpdateTime()); authPacket.setId(id); authPacket.setName(name); log.info("Client " + name + " (ID: " + (id) + ") has been authenticated. Waiting for " + (session.getNumClients() - session.getNumConnected() - 1) + " more clients"); session.setNumConnected(session.getNumConnected() + 1); boolean allConnected = false; if (session.getNumConnected() == session.getNumClients()) { allConnected = true; session.setState(SessionState.CLIENTS_FULL_STATE); } authPacket.setNumConnected(session.getNumConnected()); authPacket.setAllConnected(allConnected); return authPacket; } catch (Exception e) { log.error("Failed to authenticate client to session " + sessionId, e); } return createAuthFailedPacket("Authentication failed for unknown reason", false); } /** Attemp to re-authenticate the given client. This is called whenever a client sends an * authentication request into a session that is already running. Re-authentication will fail * if the client is not a part of the session. In this case an AUTH_FAILED authPacket is sent * back to the dispatcher. Otherwise return a RE_AUTH_SUCCESS packet to the dispatcher, which * will contain all the state information needed by the re-authenticating client */ private AuthUpdate reauthClient(int sessionId, SessionState session, String name, int dbId, Hashtable disconnected) { try { PeriodDef pinfo = session.getSession().getPeriod(session.getPeriodNum()); SubjectDef sinfo = pinfo.getSubjectInfo(); int id = sinfo.getId(dbId); if (id == -1) { return createAuthFailedPacket( "Re-authentication failed: You are not enrolled in session " + sessionId, false); } Boolean discon = (Boolean) disconnected.get(new Integer(dbId)); if (discon == null) { return createAuthFailedPacket( "Re-authentication failed: You have no connectivity status in session " + sessionId, false); } disconnected.put(new Integer(dbId), Boolean.FALSE); AuthUpdate authPacket = new AuthUpdate(AuthUpdate.RE_AUTH_SUCCESS); authPacket.setUpdateTime(session.getUpdateTime()); authPacket.setId(id); authPacket.setName(name); authPacket.setPeriodInfo(pinfo); if (!discon.booleanValue()) { authPacket.setReplacement(true); } else { authPacket.setReplacement(false); } log.info("Client " + name + " (ID: " + (id) + ") is re-authenticating into session " + sessionId); return authPacket; } catch (Exception e) { log.error("Failed to re-authenticate client to session " + sessionId, e); } return createAuthFailedPacket("Re-authentication failed for unknown reason", false); } /** Create and return an AuthUpdate with the given error message */ private AuthUpdate createAuthFailedPacket(String msg, boolean retry) { AuthUpdate failPacket = new AuthUpdate(AuthUpdate.AUTH_FAILED); failPacket.setErrorMsg(msg); failPacket.setRetry(retry); return failPacket; } /** Start the session that corresponds to the given session ID */ public boolean startSession(int sessionId) { log.debug("ControlServ is starting session " + sessionId); SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot start session " + sessionId + " -- that session does not exist!"); return false; } int state = session.getState(); if (state == SessionState.GAME_RUNNING_STATE || state == SessionState.SHUTDOWN_STATE) { log.error( "Cannot start session " + sessionId + " -- that session is either shutdown or already running"); return false; } session = trimAndInitSession(session); log.info("All clients have connected or have been trimmed -- starting session..."); session.setState(SessionState.GAME_RUNNING_STATE); session.setPeriodNum(-1); dbw.writeSessionEvent(sessionId, JMConstants.ACTION_START); return true; } /** Return true if the given session is currently running -- that is, if it not only valid but * has also started */ public boolean isSessionRunning(int sessionId) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot check running status of session " + sessionId + " -- that session does not exist!"); return false; } if (session.getState() == SessionState.GAME_RUNNING_STATE) return true; else return false; } /** Trim away all clients who are not connected from the given session. Then create all the * SessionState data objects that are dependant on the number of clients (i.e. fixed length * arrays). Return the modified SessionState object */ private SessionState trimAndInitSession(SessionState sessionState) { int numConnected = sessionState.getNumConnected(); int numClients = sessionState.getNumClients(); if (numConnected < numClients) { log.info("Trimming the " + (numClients - numConnected) + " clients who have not yet connected"); sessionState.trimClients(numConnected); } numClients = sessionState.getNumClients(); EarningsInfo[] earningsHistory = new EarningsInfo[numClients]; for (int i = 0; i < earningsHistory.length; i++) earningsHistory[i] = new EarningsInfo(); sessionState.setEarningsHistory(earningsHistory); Trader[][] traders = new Trader[sessionState.getSession().getNumPeriods()][numClients]; sessionState.setTraders(traders); return sessionState; } /** Checks to see if the session ID in the given request is valid. Returns * true if the ID is valid. Returns false otherwise */ public boolean isSessionValid(int sessionId) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session != null) return true; return false; } /** Updates the times of the given session. Called from the dispatcher when it * receives an update from the JMTimer */ public boolean updateTimers(int sessionId, int openDelay, int periodLength, int[] marketLength) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot update timers of session " + sessionId + " -- that session does not exist"); return false; } session.setPeriodLength(periodLength); session.setOpenDelay(openDelay); session.setMarketLength(marketLength); return true; } /** Set the Trader objects for the given period. These objects hold all the transaction information * of the clients */ public void setTraders(int sessionId, int period, Trader[] traders) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot set traders of session " + sessionId + " -- that session does not exist"); return; } for (Trader t : traders) { t.setCacheOrders(session.getSession().isShowPastOrders()); t.setCacheTransactions(session.getSession().isShowPastTransactions()); t.setClosebook(session.getSession().getPeriod(period).isClosebook()); t.setShowSuggestedClearingPrice(session.getSession().getPeriod(period).isShowSuggestedClearingPrice()); } session.setTraders(period, traders); } /** Updates the closure status of the given market in the given session. Called from the * dispatcher when it receives a market closure update from the JMTimer */ public boolean closeMarket(int sessionId, int market) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot close market of session " + sessionId + " -- that session does not exist"); return false; } session.setMarketClosed(market); return true; } /** Updates the closure status of the current period in the given session. Called from the * dispatcher when it receieves a period closure update from the JMTimer. Return true * if this was the last period */ public boolean closePeriod(int sessionId) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot close period of session " + sessionId + " -- that session does not exist!"); return false; } session.setPeriodClosed(true); dbw.writePeriodEvent(sessionId, session.getPeriodNum(), JMConstants.ACTION_FINISH); return isLastPeriod(sessionId); } /** Returns the current period number of the given session */ public int getPeriodNum(int sessionId) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot get period number of session " + sessionId + " -- that session does not exist!"); return -1; } int periodNum = session.getPeriodNum(); return periodNum; } public boolean getManualControl(int sessionId) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot get manual control boolean in session " + sessionId + " -- that session does not exist!"); } return session.isManualControl(); } public void setManualControl(int sessionId, boolean manual) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot set manual control boolean in session " + sessionId + " -- that session does not exist!"); } session.setManualControl(manual); } /** Returns the amount of period time remaining in the given session */ public int getPeriodTime(int sessionId) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot get period time remaining in session " + sessionId + " -- that session does not exist!"); return -1; } return session.getPeriodLength(); } /** Returns the amount of market opening time remaining in the given session */ public int getOpeningTime(int sessionId) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot get opening time remaining in session " + sessionId + " -- that session does not exist!"); return -1; } return session.getOpenDelay(); } /** Returns the amount of market time remaining in the given session for each market */ public int[] getMarketTime(int sessionId) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot get market time remaining in session " + sessionId + " -- that session does not exist!"); return null; } return session.getMarketLength(); } /** Returns an array of the names of the clients participating in the given session. These * are indexed by the system id number (not the database id number) */ public String[] getSubjectNames(int sessionId) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot get subject names for session " + sessionId + " -- that session does not exist!"); return null; } int periodNum = session.getPeriodNum(); SubjectDef sinfo = session.getSession().getPeriod(periodNum).getSubjectInfo(); return sinfo.getNames(); } /** Returns the EarningsInfo array for the given session */ public EarningsInfo[] getEarningsHistory(int sessionId) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot get earnings history for session " + sessionId + " -- that session does not exist!"); return null; } return session.getEarningsHistory(); } /** Returns the market closure status for the given session */ public boolean[] getMarketStatus(int sessionId) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot get market status for session " + sessionId + " -- that session does not exist!"); return null; } return session.getMarketClosed(); } /** Returns the period closure status for the given session */ public boolean isPeriodClosed(int sessionId) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot get period status session " + sessionId + " -- that session does not exist!"); return false; } return session.isPeriodClosed(); } /** Returns true if the current period for the given session is the last period of that session */ public boolean isLastPeriod(int sessionId) { SessionState session = (SessionState) sessionStates.get(new Integer(sessionId)); if (session == null) { log.error("Cannot check last period of session " + sessionId + " -- that session does not exist!"); return false; } int periodNum = session.getPeriodNum(); int numPeriods = session.getSession().getNumPeriods(); if (periodNum < (numPeriods - 1)) return false; else return true; } /** Calculate the payoffs for the current period in the given session. Return a PayoffUpdate * object that contains an array of the payoffs calculated and a payoff mask string, which * can be sent to the subjects to mask their payoff from them */ public PayoffUpdate calculatePayoffs(int sessionId) { SessionState sessionState = (SessionState) sessionStates.get(new Integer(sessionId)); if (sessionState == null) { log.error("Cannot calculate payoffs for session " + sessionId + " -- that session does not exist!"); return null; } int periodNum = sessionState.getPeriodNum(); SessionDef session = sessionState.getSession(); PeriodDef period = session.getPeriod(periodNum); GroupDef ginfo = period.getGroupInfo(); SubjectDef sinfo = period.getSubjectInfo(); MarketDef minfo = period.getMarketInfo(); EarningsInfo[] earningsHistory = sessionState.getEarningsHistory(); Trader[][] traders = sessionState.getTraders(); int numSubjects = sinfo.getNumSubjects(); float[] payoffs = new float[numSubjects]; float[] dividends = new float[numSubjects]; String[] payoffMasks = new String[numSubjects]; int[][] holdings = new int[numSubjects][minfo.getNumMarkets()]; float[] cash = new float[numSubjects]; for (int i = 0; i < numSubjects; i++) { int subjectId_db = sinfo.getDatabaseId(i); int group = sinfo.getGroup(i); for (int m = 0; m < minfo.getNumMarkets(); m++) { holdings[i][m] = dbw.getSecurityHoldings(subjectId_db, m, minfo); } cash[i] = dbw.getCashHoldings(sessionId, periodNum, subjectId_db); PayoffFunction payoffFunction = ginfo.getPayoffFunction(group); float payout = 0f; String payoffMask = null; try { payout = payoffFunction.getPayoff(i, periodNum, session, traders); payoffMask = payoffFunction.getPayoffMask(); } catch (Exception e) { log.error("Failed to calculate the payoff of the clients in session " + sessionId + " -- assigning zero payoff (check payoff function for errors)", e); payout = 0f; } if (ginfo.getAddDividend(group, 0)) {//if (minfo.isAddDividend(0)) { //all or nothing case only for now, so look at first security payoffs[i] = 0; dividends[i] = payout; } else { payoffs[i] = payout; dividends[i] = 0; } payoffMasks[i] = payoffMask; log.info("Payoff for player " + i + " for period " + periodNum + " is " + payoffs[i]); log.info("Dividends for player " + i + " for period " + periodNum + " are " + dividends[i]); } sessionState.setDividends(dividends); try { dbw.writePayoffs(sessionId, periodNum, payoffs, sinfo); } catch (Exception e) { log.error("Failed to write payoffs to database for session " + sessionId + " period " + periodNum, e); } float[] cumulativePayoffs = new float[numSubjects]; for (int i = 0; i < numSubjects; i++) { cumulativePayoffs[i] = dbw.getCumulativePayoff(sessionId, i, sinfo); for (int m = 0; m < holdings[i].length; m++) { EarningsRow erow = new EarningsRow(); erow.setPeriod(periodNum); erow.setSecurity(minfo.getMarketTitles()[m]); erow.setHoldings(holdings[i][m]); erow.setNumPurchases(traders[periodNum][i].getTotalPurchases(m)); erow.setNumSales(traders[periodNum][i].getTotalSales(m)); earningsHistory[i].addRow(erow); } EarningsRow erow = new EarningsRow(); erow.setPeriod(periodNum); erow.setSecurity("Cash"); erow.setHoldings(cash[i]); earningsHistory[i].addRow(erow); if (payoffMasks[i] == null) { EarningsRow cpay = new EarningsRow(); cpay.setSecurity(null); cpay.setCumPayoff(cumulativePayoffs[i]); earningsHistory[i].addRow(cpay); } } //return new PayoffUpdate(payoffs, payoffMasks); //uncomment this to send period payoff return new PayoffUpdate(cumulativePayoffs, payoffMasks); } /** Returns the payoff amount that was carried over from the period PREVIOUS to the given * period. Returns 0 if the given period is the first period. The return value of this function * depends on the settings for addDividend, which determines whether payoffs are carried over * to the next period or instead written to the payoffs table. It also depends on the addCash * variable, which determines whether leftover cash from the previous round is carried over to * the next period. Indexed by subject id */ private float[] getCarryOverCash(int sessionId, int periodNum) { SessionState state = (SessionState) sessionStates.get(new Integer(sessionId)); if (state == null) { log.error("Cannot generate carry over cash for session " + sessionId + " -- that session does not exist!"); return null; } float[] carryOver = state.getDividends(); if (carryOver == null || periodNum == 0) { carryOver = new float[state.getNumClients()]; } else { PeriodDef[] periods = state.getSession().getPeriods(); GroupDef lastGroupInfo = periods[periodNum - 1].getGroupInfo(); SubjectDef lastSubjectInfo = periods[periodNum - 1].getSubjectInfo(); for (int i = 0; i < carryOver.length; i++) { int subjectId_db = state.getDatabaseId(i); int lastGroup = lastSubjectInfo.getGroup(i); if (lastGroupInfo.getAddCash(lastGroup)) carryOver[i] += dbw.getCashHoldings(sessionId, periodNum - 1, subjectId_db); } } return carryOver; } /** Returns the security holdings carried over from the period PREVIOUS to the given period. * Returns an array of zeros if the given period is the first period. The return value of this * function depends on the settings for addSurplus, which determines whether securities are * carried over to the next period or simply whisked away. Finally, for every security in the current * period that does not match a security in the previous period (by security id in the securities * database) return 0 for the carry over of that security. Indexed by subject id then security id */ private int[][] getCarryOverSecurities(SessionState state, int periodNum) { PeriodDef[] periods = state.getSession().getPeriods(); int numClients = state.getNumClients(); MarketDef currentMarket = periods[periodNum].getMarketInfo(); int numMarkets = currentMarket.getNumMarkets(); int[][] carryOver = new int[numClients][numMarkets]; if (periodNum == 0) return carryOver; MarketDef lastMarket = periods[periodNum - 1].getMarketInfo(); GroupDef lastGroupInfo = periods[periodNum - 1].getGroupInfo(); SubjectDef lastSubjectInfo = periods[periodNum - 1].getSubjectInfo(); for (int i = 0; i < carryOver.length; i++) { int subjectId_db = state.getDatabaseId(i); int lastGroup = lastSubjectInfo.getGroup(i); for (int m = 0; m < carryOver[i].length; m++) { int securityId_db = currentMarket.getSecurityId(m); int previousMarketId = lastMarket.getMarketId(securityId_db); boolean addSurplus = false; if (previousMarketId != -1) addSurplus = lastGroupInfo.getAddSurplus(lastGroup, previousMarketId); int previousHoldings = 0; if (addSurplus) previousHoldings = dbw.getSecurityHoldings(subjectId_db, previousMarketId, lastMarket); carryOver[i][m] = previousHoldings; } } return carryOver; } /** Move the given session to the next period. Return a NewPeriodUpdate object, which encapsulates * all the information about the period that will be needed by the clients */ public NewPeriodUpdate nextPeriod(int sessionId) { SessionState sessionState = (SessionState) sessionStates.get(new Integer(sessionId)); if (sessionState == null) { log.error("Cannot move session " + sessionId + " to next period -- that session does not exist!"); return null; } int curPeriodNum = sessionState.getPeriodNum(); //move to next period int periodNum = curPeriodNum + 1; sessionState.setPeriodClosed(false); sessionState.resetMarketClosed(periodNum); sessionState.setPeriodNum(periodNum); SessionDef session = sessionState.getSession(); PeriodDef periodInfo = session.getPeriod(periodNum); SubjectDef subjectInfo = periodInfo.getSubjectInfo(); GroupDef groupInfo = periodInfo.getGroupInfo(); MarketDef marketInfo = periodInfo.getMarketInfo(); if (curPeriodNum >= 0 && sessionState.getSession().getPeriod(curPeriodNum).isApplyTrigger()) { adjustMarketAnchorPrices(sessionId, curPeriodNum, marketInfo); } EarningsInfo[] earningsHistory = sessionState.getEarningsHistory(); int[][] surplus = null; float[] dividends = null; try { dbw.writePeriod(sessionId, periodNum, periodInfo); dbw.writeSecurities(sessionId, periodNum, marketInfo); dbw.writeGroups(groupInfo); dbw.writeSubjectGroups(sessionId, periodNum, subjectInfo, groupInfo); surplus = getCarryOverSecurities(sessionState, periodNum); dividends = getCarryOverCash(sessionId, periodNum); dbw.writeCashInitials(sessionId, periodNum, subjectInfo, groupInfo, dividends); dbw.writeSecurityInitials(sessionId, periodNum, marketInfo, subjectInfo, groupInfo, surplus); dbw.writeSecurityRules(sessionId, periodNum, marketInfo, groupInfo); dbw.writeSecurityPriveleges(sessionId, periodNum, marketInfo, groupInfo); dbw.writeFunctions(sessionId, periodNum, groupInfo); } catch (SQLException e) { log.error("Failed to access database while writing next period information", e); return null; } int[][] initialHoldings = new int[sessionState.getNumClients()][marketInfo.getNumMarkets()]; float[] initialCash = new float[sessionState.getNumClients()]; for (int i = 0; i < initialHoldings.length; i++) { for (int m = 0; m < initialHoldings[i].length; m++) { initialHoldings[i][m] = groupInfo.getSecurityInitial(subjectInfo.getGroup(i), m) + surplus[i][m]; } initialCash[i] = groupInfo.getCashInitial(subjectInfo.getGroup(i)) + dividends[i]; } NewPeriodUpdate npu = new NewPeriodUpdate(); npu.setRecipients(getSessionClients(sessionId)); npu.setPeriodNum(periodNum); npu.setPeriodInfo(periodInfo); npu.setEarningsHistory(earningsHistory); npu.setInitialCash(initialCash); npu.setInitialHoldings(initialHoldings); npu.setTimeoutLength(session.getTimeoutLength()); npu.setMarketEngine(periodInfo.getMarketEngine()); dbw.writePeriodEvent(sessionId, periodNum, JMConstants.ACTION_START); return npu; } /** End the given session. Remove the experiment monitor and the session state object */ public boolean terminateSession(int sessionId) { try { sessionStates.remove(new Integer(sessionId)); dbw.writeSessionEvent(sessionId, JMConstants.ACTION_FINISH); log.info("Controlserv has successfully terminated session " + sessionId); return true; } catch (Exception e) { log.error("Server failed to terminate the session " + sessionId, e); } return false; } private void adjustMarketAnchorPrices(int sessionId, int curPeriodNum, MarketDef marketInfo) { for (int i = 0; i < marketInfo.getNumMarkets(); i++) { String marketName = marketInfo.getMarketTitles()[i]; float avg = dbw.getAvgTransactionPrice(sessionId, curPeriodNum, marketName); float trigger = marketInfo.getMaxPrices()[i] - 0.2f * (marketInfo.getMaxPrices()[i] - marketInfo.getMinPrices()[i]); if (log.isDebugEnabled()) { log.debug("Market " + marketName + " trigger check, avg: " + avg + "; trigger: " + trigger); } //do anchor price adjustment if avg > trigger price if (avg > trigger) { float newHigh = marketInfo.getMaxPrices()[i] + 0.2f * (marketInfo.getMaxPrices()[i] - marketInfo.getMinPrices()[i]); float newLow = marketInfo.getMinPrices()[i] + 0.2f * (marketInfo.getMaxPrices()[i] - marketInfo.getMinPrices()[i]); marketInfo.getMaxPrices()[i] = newHigh; marketInfo.getMinPrices()[i] = newLow; marketInfo.generatePrices(); } } } /** Database access writer */ public static DBWriter dbw; /** Hashtable, keyed by session ID, that contains the SessionState objects corresponding to the ID */ private Hashtable sessionStates; private static Log log = LogFactory.getLog(ControlServ.class); }