edu.caltechUcla.sselCassel.projects.jMarkets.server.control.DispatchServ.java Source code

Java tutorial

Introduction

Here is the source code for edu.caltechUcla.sselCassel.projects.jMarkets.server.control.DispatchServ.java

Source

/*
 * 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
 */

/*
 * DispatchServ.java
 *
 * Created on February 3, 2005, 11:24 AM
 */

package edu.caltechUcla.sselCassel.projects.jMarkets.server.control;

import java.util.*;
import org.apache.log4j.*;
import java.rmi.server.*;
import java.net.*;
import java.io.*;
import edu.caltechUcla.sselCassel.projects.jMarkets.server.updates.*;
import edu.caltechUcla.sselCassel.projects.jMarkets.server.network.*;
import edu.caltechUcla.sselCassel.projects.jMarkets.shared.JMConstants;
import edu.caltechUcla.sselCassel.projects.jMarkets.shared.network.*;
import edu.caltechUcla.sselCassel.projects.jMarkets.shared.data.*;
import edu.caltechUcla.sselCassel.projects.jMarkets.shared.data.model.earningstable.EarningsInfo;
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.offers.AbstractOffer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author  Raj Advani, Walter M. Yuan
 */
public class DispatchServ implements Dispatcher {

    /** Creates a new instance of DispatchServ */
    public DispatchServ(Properties props, String receiverPath) {
        this.receiverPath = receiverPath;

        controlServ = new ControlServ(props);
        updateServ = new HTTPUpdateServ();

        continuousMarketEngine = new ContinuousMarketEngine(controlServ.getDBWriter());
        callMarketEngine = new CallMarketEngine(controlServ.getDBWriter());
        activeEngines = new Hashtable<Integer, TradeEngine>();

        Hashtable sessionMonitors = new Hashtable();
        monitorServ = new MonitorServ(sessionMonitors, props);
        activateMonitorTunnel(monitorServ.getMonitorProtocol(), monitorServ.getMonitorPort());

        appender = new guiAppender(monitorServ);
        Category root = Category.getRoot();
        root.addAppender(appender);

        jTimer = new JMTimer(this);

        cmon = new ConnectionMonitor(controlServ, updateServ, monitorServ);
        cmon.start();
    }

    public void destroy() {
        log.debug("DispatchServ.destroy() called...");
        // tell the connection monitor we're ending, close its threads'
        cmon.stopMonitor();
        // tell monitorServ to end
        monitorServ.done();
        // end the registration thread in this class
        stopRegThread = true;
        // tell the timers to cancel themselves
        jTimer.cancel();

        //close the two sockets in this class
        try {
            servSock.close();
            monitorSocket.close();
        } catch (IOException ioe) {
            log.warn("IOException in DispatchServ.destroy()");
            System.out.println("IOException in DispatchServ.destroy():");
            ioe.printStackTrace();
        }
    }

    /** Called when the server receives a SERVER_INIT_REQUEST from an experimenter. Dispatches this
     *  message to the ControlServ, which will create a new session and return the ID of the session
     *  created. Return a SERVER_INIT_RESPONSE, which includes the session ID number */
    public Response processInitRequest(Request req) {
        try {
            log.info("Dispatcher has received a SERVER_INIT_REQUEST -- initializing session...");

            int numClients = req.getIntInfo("numClients");
            String sessionName = req.getStringInfo("name");
            int updateTime = getUpdateTime(req);

            SessionDef session = (SessionDef) req.getInfo("session");

            int sessionId = controlServ.startNewSession(sessionName, numClients, updateTime, session);
            monitorServ.registerSession(sessionId);

            Response res = new Response(Response.SERVER_INIT_RESPONSE);
            res.addIntInfo("sessionId", sessionId);

            log.info("Processed all session initialization parameters for session " + sessionId
                    + ". Waiting for admin connection...");

            return res;

        } catch (Exception e) {
            log.error("Error processing the session initialization request", e);
        }
        return new Response(Response.INVALID_SESSION_RESPONSE);
    }

    /** Return the update time that will be used in a new session. Extract this information
     *  from the given SERVER_INIT_REQUEST */
    private int getUpdateTime(Request req) {
        if (req.getType() != Request.SERVER_INIT_REQUEST) {
            log.warn("Attempted to read update protocol information from an invalid request");
            return OPERATION_FAILED;
        }

        int updateTime = 0;
        int updateProtocol = req.getIntInfo("updateProtocol");
        if (updateProtocol == JMConstants.HTTP_UPDATE_PROTOCOL) {
            log.info("Session will be using HTTP communication protocol");
            updateTime = req.getIntInfo("updateTime");
        }
        if (updateProtocol == JMConstants.RMI_UPDATE_PROTOCOL) {
            log.info("Session will be using RMI communication protocol");
        }

        return updateTime;
    }

    /** This function is either called by the RMI GUI Tunnel registration interface or by the
     *  TCP registration thread (see activateMonitorTunnel comments). This function registers a new
     *  monitor with the system by way of its transmitter (each admin monitor is has a receiver
     *  on the admin 'client' end and a transmitter on this server end). The transmitters are used
     *  by the MonitorServ to communicate with the actual Monitors.
     *
     *  The monitor transmitter is added to the session it belongs to. One ExpMonitor is required
     *  to be linked to the session before clients can start connecting. Once the session is started
     *  by pressing the Start button on this initial monitor, it is safe to shut down the monitor.
     *  At any time new monitors can be created to view a session -- these monitors are brought
     *  up to speed with the latest client connection status, button status, metrics, and price chart
     *  of the given session */
    public void registerMonitorTransmitter(MonitorTransmitter mt, int sessionId) {
        if (monitorServ.registerExpMonitor(sessionId, mt)) {
            boolean firstMonitor = controlServ.registerExpMonitor(sessionId, mt);

            if (!firstMonitor) {
                Vector securities = null;
                NumOffersUpdate[] offerUpdates = null;
                MetricsUpdate[] metricsUpdates = null;

                TradeEngine tradeServ = activeEngines.get(sessionId);
                if (tradeServ != null) {
                    securities = tradeServ.getPriceChartView(sessionId);
                    offerUpdates = tradeServ.getNumOffers(sessionId);
                    metricsUpdates = tradeServ.getMetrics(sessionId);
                }

                String[] names = controlServ.getSubjectNames(sessionId);
                boolean enableStart = !controlServ.isSessionRunning(sessionId);

                monitorServ.updateState(sessionId, mt, securities, names, enableStart, offerUpdates,
                        metricsUpdates);
                log.info(
                        "An alternative (non-primary) ExpMonitor has been connected and brought up to state for session "
                                + sessionId);
            }
        }
    }

    /** Whenever a client or admin monitor logs in, they send a session query request to
     *  find out what sessions are currently active. This returns an array of SessionIdentifier
     *  objects so that they can display this information and choose what session to
     *  join. This also sends the monitor the communication protocol and port that it will use
     *  from this point onward to register with and communicate with the server */
    public Response processSessionQueryRequest(Request req) {
        log.debug("DispatchServ has received a SESSION_QUERY_REQUEST -- returning active session list");

        SessionIdentifier[] identifiers = controlServ.getSessionIdentifiers();

        int protocol = monitorServ.getMonitorProtocol();
        String protocolStr = "rmi";
        if (protocol == Monitor.TCP_MONITOR_PROTOCOL)
            protocolStr = "tcp";

        int port = monitorServ.getMonitorPort();

        Response res = new Response(Response.SESSION_QUERY_RESPONSE);
        res.addInfo("identifiers", identifiers);
        res.addStringInfo("protocol", protocolStr);
        res.addIntInfo("port", port);

        return res;
    }

    /** Called when the server receives a CLIENT_AUTH_REQUEST from a client. Initializes new clients
     *  by sending them their client ID numbers. Re-authenticates disconnected clients
     *  by sending all previous state information. The dispatcher collects authentication information
     *  from the client request, sends it to the ControlServ for processing, then sends out the
     *  results of the authentication process back to the clients. This method also handles
     *  re-authentication, for disconnected clients who try to reconnect to currently running
     *  sessions. In order to process re-authentication, the dispatcher collects information on
     *  client connection status from the UpdateServ and passes this to the ControlServ */
    public synchronized Response processAuthRequest(Request req) {
        try {
            log.debug("DispatchServ has received a CLIENT_AUTH_REQUEST -- processing authentication");

            String name = req.getStringInfo("name");
            int dbId = req.getIntInfo("dbId");
            int sessionId = req.getIntInfo("sessionId");

            Hashtable disconnectStatus = updateServ.getDisconnectStatus();
            AuthUpdate authPacket = controlServ.authClient(sessionId, name, dbId, disconnectStatus);

            if (authPacket.getStatus() == AuthUpdate.AUTH_FAILED) {
                Response res = new Response(Response.AUTH_REJECT_RESPONSE);
                res.addStringInfo("message", authPacket.getErrorMsg());
                res.addBooleanInfo("retry", authPacket.isRetry());

                return res;
            }

            int id = authPacket.getId();
            int updateTime = authPacket.getUpdateTime();

            Response res = new Response(Response.AUTH_CONFIRM_RESPONSE);
            res.addIntInfo("updateProtocol", JMConstants.HTTP_UPDATE_PROTOCOL);
            res.addIntInfo("updateTime", updateTime);
            res.addStringInfo("tradeServlet", receiverPath);
            res.addIntInfo("id", id);
            res.addIntInfo("sessionId", sessionId);

            if (authPacket.getStatus() == AuthUpdate.AUTH_SUCCESS) {
                int numConnected = authPacket.getNumConnected();
                boolean allConnected = authPacket.isAllConnected();

                monitorServ.updateAuthStatus(sessionId, id, name, numConnected, allConnected);
                updateServ.addClient(dbId);
            }

            if (authPacket.getStatus() == AuthUpdate.RE_AUTH_SUCCESS) {
                if (authPacket.isReplacement()) {
                    Response reauthKill = new Response(Response.REAUTH_KILL_RESPONSE);
                    updateServ.update(dbId, reauthKill);
                }

                PeriodDef period = authPacket.getPeriodInfo();
                TradeEngine tradeServ = activeEngines.get(sessionId);

                tradeServ.resetClientKey(sessionId, id);

                Response reauth = new Response(Response.REAUTH_RESPONSE);
                reauth.addInfo("offerbook", tradeServ.generateClientBook(sessionId));
                reauth.addInfo("chart", tradeServ.getPriceChartView(sessionId));
                reauth.addInfo("marketInfo", period.getMarketInfo());
                reauth.addStringInfo("marketEngine", period.getMarketEngine());
                reauth.addInfo("trader", tradeServ.getTraders(sessionId)[id]);
                reauth.addIntInfo("openDelay", controlServ.getOpeningTime(sessionId));
                reauth.addIntInfo("periodLength", controlServ.getPeriodTime(sessionId));
                reauth.addInfo("marketLength", controlServ.getMarketTime(sessionId));
                reauth.addIntInfo("periodNum", controlServ.getPeriodNum(sessionId));
                reauth.addInfo("earningsInfo", controlServ.getEarningsHistory(sessionId)[id]);
                reauth.addInfo("marketClosed", controlServ.getMarketStatus(sessionId));
                reauth.addBooleanInfo("periodClosed", controlServ.isPeriodClosed(sessionId));

                updateServ.update(dbId, reauth);
            }

            return res;
        } catch (Exception e) {
            log.error("Failed to process a CLIENT_AUTH_REQUEST to connect a client to server", e);
        }
        Response res = new Response(Response.AUTH_REJECT_RESPONSE);
        res.addStringInfo("message", "Authentication failed for unknown reason. Please try again.");
        return res;
    }

    /** Start the game in response to the administrator pressing the start button on the ExpMonitor */
    public Response processStartRequest(Request req) {
        try {
            int sessionId = req.getIntInfo("sessionId");
            boolean sessionStarted = controlServ.startSession(sessionId);

            if (sessionStarted) {
                monitorServ.setSessionRunning(sessionId, true);
                monitorServ.updateExpStatus(sessionId, "Starting Session");
                nextPeriod(sessionId);
            } else {
                monitorServ.setSessionRunning(sessionId, false);
            }

        } catch (Exception e) {
            log.error("Failed to process a START_GAME_REQUEST and move to the next period", e);
        }
        return new Response(Response.GENERAL_ACK_RESPONSE);
    }

    public Response processStartPeriodRequest(Request req) {
        try {
            int sessionId = req.getIntInfo("sessionId");

            nextPeriod(sessionId);

        } catch (Exception e) {
            log.error("Failed to process a START_GAME_REQUEST and move to the next period", e);
        }
        return new Response(Response.GENERAL_ACK_RESPONSE);
    }

    public Response processStopPeriodRequest(Request req) {
        try {
            int sessionId = req.getIntInfo("sessionId");
        } catch (Exception e) {
            log.error("Failed to process a STOP_GAME_REQUEST and move to the next period", e);
        }
        return new Response(Response.GENERAL_ACK_RESPONSE);
    }

    /** Stop the game in response to the administrator pressing the stop button on the ExpMonitor */
    public Response processStopRequest(Request req) {
        try {
            int sessionId = req.getIntInfo("sessionId");
        } catch (Exception e) {
            log.error("Failed to process a STOP_GAME_REQUEST and move to the next period", e);
        }
        return new Response(Response.GENERAL_ACK_RESPONSE);
    }

    public Response processIsManualControlRequest(Request req) {
        boolean manual = false;
        try {
            int sessionId = req.getIntInfo("sessionId");
            //log.error("processing an IS_MANUAL_CONTROL_REQUEST for sessionId="+sessionId);
            manual = controlServ.getManualControl(sessionId);
            //log.error("processing an IS_MANUAL_CONTROL_REQUEST: manual=="+manual);

        } catch (Exception e) {
            log.error("Failed to process a IS_MANUAL_CONTROL_REQUEST - returning FALSE", e);
        }
        Response res = new Response(Response.IS_MANUAL_CONTROL_RESPONSE);
        res.addBooleanInfo("manualControl", manual);
        return res;
    }

    public Response processSetManualControlRequest(Request req) {
        try {
            boolean manual = req.getBooleanInfo("mc");
            int sessionId = req.getIntInfo("sessionId");
            controlServ.setManualControl(sessionId, manual);
        } catch (Exception e) {
            log.error("Failed to process a STOP_GAME_REQUEST and move to the next period", e);
        }
        return new Response(Response.GENERAL_ACK_RESPONSE);
    }

    /** Move to the next period of the given session. Collect the information for the new period
     *  from the ControlServ, which also writes that information into the database. Send the
     *  collected information to the clients via the updateServ. Finally, start a JTimer to track
     *  time events in the new period */
    private void nextPeriod(int sessionId) {
        NewPeriodUpdate npu = controlServ.nextPeriod(sessionId);

        int[] recipients = npu.getRecipients();
        int periodNum = npu.getPeriodNum();
        PeriodDef pinfo = npu.getPeriodInfo();
        EarningsInfo[] earningsHistory = npu.getEarningsHistory();
        float[] initialCash = npu.getInitialCash();
        int[][] initialHoldings = npu.getInitialHoldings();
        int timeoutLength = npu.getTimeoutLength();
        String engine = npu.getMarketEngine();

        TradeEngine tradeServ = continuousMarketEngine;
        if (engine.equalsIgnoreCase(JMConstants.CALL_MARKET_ENGINE)) {
            tradeServ = callMarketEngine;
        }
        activeEngines.put(sessionId, tradeServ);

        Trader[] traders = tradeServ.initPeriod(sessionId, periodNum, pinfo, initialCash, initialHoldings);
        Vector securities = tradeServ.getPriceChartView(sessionId);
        OfferBook offerBook = tradeServ.generateClientBook(sessionId);

        updateServ.sendNewPeriodUpdate(recipients, periodNum, pinfo, earningsHistory, traders, offerBook);
        monitorServ.updateExpStatus(sessionId, "Initializing Period " + periodNum);
        monitorServ.updatePeriodStatus(sessionId, securities);
        controlServ.setTraders(sessionId, periodNum, traders);

        int periodLength = pinfo.getPeriodLength();
        int openDelay = pinfo.getOpenDelay();
        int[] marketLength = pinfo.getMarketInfo().getMarketTime();

        jTimer.schedulePeriodTimer(sessionId, periodNum, timeoutLength, periodLength, openDelay, marketLength);
    }

    /** JMTimer calls this method every second whenever a session's timer display should be updated. Note
     *  this is NOT called when the JMTimer wants to update (re-sync) client timers -- for that, the method
     *  processTimeEvent is called. This method ONLY updates timers on the monitors */
    public boolean setTimeLeft(int sessionId, int openDelay, int periodLength, int[] marketLength) {
        monitorServ.updateTimeLeft(sessionId, periodLength);
        return controlServ.updateTimers(sessionId, openDelay, periodLength, marketLength);
    }

    /** JMTimer calls this method every time it detects the need for a time synchronization between
     *  the server and clients. It passes in the three timers for the period. The dispatcher routes
     *  this request to the UpdateServ to inform the clients, and the ControlServ to update the session
     *  data structures. Returns the response sent out by the UpdateServ */
    public Response processTimeEvent(int sessionId, int periodNum, int openDelay, int periodLength,
            int[] marketLength) {
        int[] recipients = controlServ.getSessionClients(sessionId);
        controlServ.updateTimers(sessionId, openDelay, periodLength, marketLength);

        if (recipients != null)
            return updateServ.sendTimeUpdate(recipients, periodNum, openDelay, periodLength, marketLength);
        else
            return null;
    }

    /** JMTimer calls this method every time a market closes in the given session. The dispatcher
     *  routes this request to the UpdateServ to inform the clients, and the ControlServ to update
     *  the session data structures. Returns the response sent out by the UpdateServ */
    public Response processMarketClosure(int sessionId, int periodNum, int market) {
        int[] recipients = controlServ.getSessionClients(sessionId);
        controlServ.closeMarket(sessionId, market);

        if (recipients != null)
            return updateServ.sendMarketCloseUpdate(recipients, periodNum, market);
        else
            return null;
    }

    /**
     * JMTimer calls this method every time a period opens. This occurs when that period's market
     *  opening delay has finished. The dispatcher routes this request to the ContinuousMarketEngine
     */
    public boolean processPeriodOpening(int sessionId, int periodNum) {
        TradeEngine tradeServ = activeEngines.get(sessionId);

        return tradeServ.stampPeriodStartTime(sessionId);
    }

    /** JMTimer calls this method every time a period closes in the given session. The dispatcher
     *  routes this request to the UpdateServ to inform the clients, and the ControlServ to update
     *  the session data structures. Returns the response sent out by the UpdateServ. Before all this
     *  notify the Trade Engine that the period has closed, and process any updates that it
     *  generates (this happens predominantly in call markets, where all offers are processed upon
     *  period closure */
    public Response processPeriodClosure(int sessionId, int periodNum) {
        TradeEngine tradeServ = activeEngines.get(sessionId);

        UpdateBasket basket = tradeServ.processClosePeriod(sessionId);
        if (basket != null) {
            int periodTime = 0;
            int[] marketTime = new int[controlServ.getMarketTime(sessionId).length];
            int openingTime = controlServ.getOpeningTime(sessionId);

            Vector tradeUpdates = basket.getTradeUpdates();
            for (int i = 0; i < tradeUpdates.size(); i++) {
                TradeUpdate tupdate = (TradeUpdate) tradeUpdates.get(i);
                tupdate.setPeriodTime(periodTime);
                tupdate.setMarketTime(marketTime);
                tupdate.setOpeningTime(openingTime);
            }

            updateServ.sendTradeUpdates(periodNum, tradeUpdates);
            monitorServ.processTransactionUpdates(basket);
        }

        int[] recipients = controlServ.getSessionClients(sessionId);
        boolean lastPeriod = controlServ.closePeriod(sessionId);

        if (recipients == null)
            return null;

        PayoffUpdate pupdate = controlServ.calculatePayoffs(sessionId);
        float[] payoffs = pupdate.getPayoffs();
        String[] masks = pupdate.getMasks();

        EarningsInfo[] einfo = controlServ.getEarningsHistory(sessionId);

        if (lastPeriod) {
            updateServ.sendEndPeriodUpdate(recipients, periodNum, payoffs, masks, einfo, true);
            log.info("Subjects have completed all periods. Ending session in 30 seconds...");
            terminateSession(sessionId, 30000);
        } else {
            log.info("Moving to the next period (period " + (periodNum + 1) + ")");
            updateServ.sendEndPeriodUpdate(recipients, periodNum, payoffs, masks, einfo, false);
            if (!controlServ.getManualControl(sessionId)) {
                nextPeriod(sessionId);
            }
        }
        return null;
    }

    /** Called whenever a client attempts to make a trade */
    public Response processTransactionRequest(Request req) {
        if (!isSessionValid(req))
            return new Response(Response.INVALID_SESSION_RESPONSE);

        int sessionId = req.getIntInfo("sessionId");
        AbstractOffer newOffer = (AbstractOffer) req.getInfo("offer");
        long key = req.getLongInfo("key");
        int periodNum = controlServ.getPeriodNum(sessionId);
        TradeEngine tradeServ = activeEngines.get(sessionId);

        UpdateBasket basket = tradeServ.processOffer(sessionId, newOffer, key);

        int periodTime = controlServ.getPeriodTime(sessionId);
        int[] marketTime = controlServ.getMarketTime(sessionId);
        int openingTime = controlServ.getOpeningTime(sessionId);

        Vector tradeUpdates = basket.getTradeUpdates();
        for (int i = 0; i < tradeUpdates.size(); i++) {
            TradeUpdate tupdate = (TradeUpdate) tradeUpdates.get(i);
            tupdate.setPeriodTime(periodTime);
            tupdate.setMarketTime(marketTime);
            tupdate.setOpeningTime(openingTime);
        }

        updateServ.sendTradeUpdates(periodNum, tradeUpdates);
        monitorServ.processTransactionUpdates(basket);

        return new Response(Response.GENERAL_ACK_RESPONSE);
    }

    /** Respond to a TERMINATE_SESSION_REQUEST by ending the current session and sending back
     *  a general acknowledgment */
    public Response processTerminateRequest(Request req) {
        int sessionId = req.getIntInfo("sessionId");
        log.info("Server has received a TERMINATE_SESSION_REQUEST for session " + sessionId + " -- ending session");
        terminateSession(sessionId, 0);

        return new Response(Response.GENERAL_ACK_RESPONSE);
    }

    /** Terminate the given session. Delete the clients involved in the session from the roster
     *  of the updateServ, shut down all remaining JTimers associated with the session, and
     *  tell the controlServ to drop all information regarding the session. Return true if
     *  termination succeeds. Wait the given number of seconds before terminating. This is needed
     *  because if we terminate immediately after a session ends, the clients may not have time
     *  to get the END_PERIOD_UPDATES */
    private synchronized boolean terminateSession(int sessionId, int waitTime) {
        try {
            Timer timer = new Timer();
            final int sid = sessionId;
            final TradeEngine tradeServ = activeEngines.get(sid);

            TimerTask task = new TimerTask() {
                public void run() {
                    int[] clients = controlServ.getSessionClients(sid);
                    jTimer.terminateSession(sid);
                    updateServ.deleteClients(clients);
                    controlServ.terminateSession(sid);
                    monitorServ.terminateSession(sid);
                    tradeServ.terminateSession(sid);

                    log.info("Session " + sid + " fully terminated, all remaining clients will be deactivated");
                }
            };

            timer.schedule(task, waitTime);

            return true;
        } catch (Exception e) {
            log.error("Failed to terminate session " + sessionId, e);
        }
        return false;
    }

    /** Checks to see if the session ID in the given request is valid. Returns true if the
     *  true if the ID is valid. Returns false otherwise. Usually, this will prompt the
     *  the DispatchServ to send a SESSION_INVALID_RESPONSE to the clieint. A
     *  SESSION_INVALID_RESPONSE, when read by a client in response to an update request,
     *  tells that client to stop sending update requests. This is important because antiquated,
     *  non-disconnected clients can slow down a server with these requests */
    private boolean isSessionValid(Request req) {
        int sessionId = req.getIntInfo("sessionId");
        return controlServ.isSessionValid(sessionId);
    }

    /** Return an update in response to a client's request */
    public Response processUpdateRequest(Request req) {
        if (!isSessionValid(req))
            return new Response(Response.INVALID_SESSION_RESPONSE);

        return updateServ.getUpdate(req);
    }

    /** Called when a request of invalid type is received */
    public Response processUnknownRequest(Request req) {
        log.warn("Received an unknown request. Responding with general acknowledgement");
        return new Response(Response.GENERAL_ACK_RESPONSE);
    }

    /** This readies the DispatchServ to receive registration connections from admin monitor GUIs.
     *  The action of this method depends on the protocol being used by the monitors. See the specific
     *  protocol methods for more details on this. Regardless of protocol, what happens is the following:
     *  a persistant interface is set up to listen for monitor receivers to connect. When monitor receivers
     *  attempt to connect to the server, a monitor transmitter is created for them. This transmitter is
     *  then registered with the MonitorServ so that the MonitorServ can communicate directly with the
     *  monitors. The control flow becomes:
     *
     *  MonitorServ --(one to many)--> Transmitters --(one to one) --> Receivers --(one to one)--> Monitors
     */
    private int activateMonitorTunnel(int protocol, int port) {
        if (protocol == Monitor.RMI_MONITOR_PROTOCOL) {
            return activateRMIMonitorTunnel(port);
        }

        else if (protocol == Monitor.TCP_MONITOR_PROTOCOL) {
            return activateTCPMonitorTunnel(port);
        }

        else
            return OPERATION_FAILED;
    }

    /** RMI: This method turns on an RMI server that waits for an RMIMonitor interfaces (specifically,
     *  RMIMonitorReceivers) to register themselves with the MonitorServ. Each receiver that registers
     *  is assigned a new RMIMonitorTransmitter which is used by the MonitorServ to communicate to the
     *  receiver, which in turn manipulates the monitor on the admin side.
     */
    private int activateRMIMonitorTunnel(int port) {
        try {
            tunnel = new GUITunnel(this);
            log.info("DispatchServ has created a GUITunnel object and linked it to the Dispatcher");

            UnicastRemoteObject.exportObject((GUILink) tunnel);
            log.info("DispatchServ has exported the GUITunnel in the form of a GUILink object");

            registry = new SimpleObjectRegistry(port, port + 20);

            registry.rebind("link", (GUILink) tunnel);
            log.info("DispatchServ has successfully bound the Monitor Registrar onto port " + registry.port());

            return registry.port();
        } catch (java.rmi.UnknownHostException uhe) {
            log.error("The host computer name you have specified for RMI monitor communication is invalid", uhe);
            return OPERATION_FAILED;
        } catch (Exception re) {
            log.error("Error exporting GUITunnel for use in RMI monitor communication", re);
            return OPERATION_FAILED;
        }
    }

    /** TCP: This method opens up a ServerSocket at the given port then starts a thread that waits
     *  for TCPMonitorReceivers to attempt to connect. Each connecting TCPMonitorReceiver creates an
     *  individual Socket connection, which is then attached to a TCPMonitorTransmitter. The MonitorServ
     *  uses the transmitter to communicate with the receiver, which in turn manipulates the monitor.
     */
    private int activateTCPMonitorTunnel(int port) {
        try {
            log.info("DispatchServ is creating a TCP ServerSocket on port " + port
                    + " for use in server-monitor communication");
            servSock = new ServerSocket(port);

            Runnable registrar = new Runnable() {

                public void run() {

                    while (!stopRegThread) {
                        try {
                            monitorSocket = servSock.accept();

                            log.debug(
                                    "Communication socket accepted from new monitor, opening input/output streams...");

                            ObjectInputStream inputStream = new ObjectInputStream(monitorSocket.getInputStream());
                            ObjectOutputStream outputStream = new ObjectOutputStream(
                                    monitorSocket.getOutputStream());

                            outputStream.flush();

                            log.debug("Input stream opened, reading session ID from connecting monitor");
                            int sessionId = inputStream.readInt();
                            log.debug("Session ID " + sessionId
                                    + " read from new monitor socket, pairing TCP receiver with a transmitter");

                            MonitorTransmitter transmitter = new TCPMonitorTransmitter(monitorSocket, inputStream,
                                    outputStream);
                            registerMonitorTransmitter(transmitter, sessionId);
                        } catch (Exception e) {
                            log.warn(
                                    "Error while accepting a monitor socket connection -- returning to 'waiting for accept' state",
                                    e);
                            System.out.println(
                                    "Error while accepting a monitor socket connection -- returning to 'waiting for accept' state");
                            e.printStackTrace();
                            continue;
                        }
                    }
                    /*log.error("exited while loop");
                    try{
                    monitorSocket.close();
                    } catch( IOException ioe ){
                    log.warn("Error while trying to close monitorSocket");
                    ioe.printStackTrace();
                    }*/
                }
            };

            registrationThread = new Thread(registrar);
            registrationThread.setDaemon(true);
            registrationThread.start();

            return port;
        } catch (Exception re) {
            log.error("Error activating ServerSocket for use in TCP monitor communication", re);
            return OPERATION_FAILED;
        }
    }

    /** Controls the data and session state */
    private Controller controlServ;

    /** Controls all handling of updates to clients */
    private Updater updateServ;

    /** Maps session numbers to their currently active market engines */
    private Map<Integer, TradeEngine> activeEngines;

    /** Controls all continuos market transactions */
    private TradeEngine continuousMarketEngine;

    /** Controls all call market transactions */
    private TradeEngine callMarketEngine;

    /** Keeps track of and updates the experiment monitors for each session */
    private Monitor monitorServ;

    /** Handles all JMarkets timer events, the only class other than the Receiver that can
     *  call methods on the Dispatcher */
    private JMTimer jTimer;

    /** The appender used to log to the GUI */
    private guiAppender appender;

    /** The path used by clients to access the Servlet Receiver */
    private String receiverPath;

    /** Used to initialize links with the ExpMonitor interfaces */
    private GUITunnel tunnel;

    /** Registry used for exporting the GUITunnel objects via RMI */
    private SimpleObjectRegistry registry;

    private ConnectionMonitor cmon;

    private Thread registrationThread;
    private volatile boolean stopRegThread = false;
    private Socket monitorSocket;
    private ServerSocket servSock;

    private static int OPERATION_FAILED = -1;

    private static Log log = LogFactory.getLog(DispatchServ.class);
}