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 */ /* * MonitorServ.java * * Created on February 3, 2005, 4:03 PM */ package edu.caltechUcla.sselCassel.projects.jMarkets.server.control; import java.util.*; import edu.caltechUcla.sselCassel.projects.jMarkets.server.updates.*; import edu.caltechUcla.sselCassel.projects.jMarkets.server.network.*; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * * @author Raj Advani */ public class MonitorServ implements Monitor { /** Creates a new instance of MonitorServ */ public MonitorServ(Hashtable sessionMonitors, Properties props) { this.sessionMonitors = sessionMonitors; readProperties(props); log.info("MonitorServ starting auxiliary update threads..."); startMetricsUpdateThread(); startPriceChartUpdateThread(); startNumOffersUpdateThread(); startOfferBacklogThread(); } public void done() { log.error("MonitorServ.done() called..."); metricsUpdateDone = true; priceChartDone = true; numOffersDone = true; offerBacklogDone = true; } /** Read the properties required for the MonitorServ. These are the monitorPort and * the monitorProtocol, which are used to determine the style of network communication * with the monitors */ private void readProperties(Properties props) { String monitorProtocolStr = props.getProperty("monitorProtocol", "tcp"); if (monitorProtocolStr.equalsIgnoreCase("rmi")) { monitorProtocol = RMI_MONITOR_PROTOCOL; log.info("Using the RMI protocol for server-monitor communication"); } else { monitorProtocol = TCP_MONITOR_PROTOCOL; log.info("Using the TCP protocol for server-monitor communication"); } String monitorPortStr = props.getProperty("monitorPort", "5000"); try { monitorPort = Integer.parseInt(monitorPortStr); log.info("Using port " + monitorPort + " for server-monitor communication"); } catch (Exception e) { log.warn("Invalid monitor port property read: " + monitorPortStr + "; using default port 5000"); monitorPort = 5000; } } /** Add a given session to the monitor serv. Once this is done the monitor serv is ready to * accept MonitorTransmitters for that session */ public void registerSession(int sessionId) { sessionMonitors.put(new Integer(sessionId), new Vector()); } /** Terminate the given session in the monitor interface by removing all monitors associated * with the given session. Also disable the stop button in all the montiors of that session */ public void terminateSession(int sessionId) { Vector monitors = getMonitors(sessionId); if (monitors == null) { log.warn("Cannot update MonitorTransmitters with session terminated status for session " + sessionId + " -- that session does not exist!"); return; } for (int i = 0; i < monitors.size(); i++) { MonitorTransmitter ui = (MonitorTransmitter) monitors.get(i); try { ui.setStopExpButtonEnabled(false); } catch (MonitorDisconnectedException e) { log.error( "Failed to establish connection with MonitorTransmitter -- disconnecting from failed monitor"); disconnectMonitor(sessionId, ui); } } sessionMonitors.remove(new Integer(sessionId)); } /** Register the given MonitorTransmitter interface with the given session ID. Return true if the operation * is successful. Each MonitorTransmitter communicates with a unique monitor */ public boolean registerExpMonitor(int sessionId, MonitorTransmitter ui) { Vector monitors = (Vector) sessionMonitors.get(new Integer(sessionId)); if (monitors == null) { log.warn("Cannot register a monitor with session " + sessionId + " -- that session has not been initialized"); return false; } monitors.add(ui); log.info("Experiment monitor has been registered for session " + sessionId); return true; } /** Disconnect the given monitor from the given session. This is called whenever a MonitorDisconnectedException * is thrown, indicating the the server has lost connection with the given monitor */ private boolean disconnectMonitor(int sessionId, MonitorTransmitter ui) { Vector monitors = (Vector) sessionMonitors.get(new Integer(sessionId)); if (monitors == null) { log.warn("Cannot disconnect a monitor in session " + sessionId + " -- that session has not been initialized"); return false; } try { ui.close(); } catch (MonitorDisconnectedException e) { } return monitors.remove(ui); } /** Sets the state of the given ExpMonitor to reflect the given session. The dispatcher calls * this function for all ExpMonitors that begin monitoring a session after the first one. These * monitors need to have the latest price chart, the latest connection status, and the latest * button status (start button). Also update metrics information */ public void updateState(int sessionId, MonitorTransmitter ui, Vector chartSecurities, String[] names, boolean enableStart, NumOffersUpdate[] offerUpdates, MetricsUpdate[] metricsUpdates) { try { if (enableStart) ui.setStartExpButtonEnabled(true); else { ui.setStartExpButtonEnabled(false); ui.setStopExpButtonEnabled(true); } if (chartSecurities != null) { ui.insertPriceChart(chartSecurities); ui.constructInfoPeriodPanel(); } if (names != null) { for (int i = 0; i < names.length; i++) { if (names[i] != null) ui.connectClient(i, names[i]); else { ui.connectClient(i, "<html><font color=#003366>Client " + i + "</font></html>"); ui.setConnected(i, false); } } } if (offerUpdates != null) { for (int i = 0; i < offerUpdates.length; i++) { ui.updateNumOffers(offerUpdates[i].client, offerUpdates[i].numOffers); } } if (metricsUpdates != null) { for (int i = 0; i < metricsUpdates.length; i++) { ui.updateMetrics(metricsUpdates[i].iterations, metricsUpdates[i].numTrans, metricsUpdates[i].average); } } } catch (MonitorDisconnectedException e) { log.error( "Failed to establish connection with MonitorTransmitter -- disconnecting from failed monitor"); disconnectMonitor(sessionId, ui); } } /** Update the server monitors with a new client connection. This is called whenever a new * client is authenticated */ public void updateAuthStatus(int sessionId, int id, String name, int numConnected, boolean allConnected) { Vector monitors = getMonitors(sessionId); if (monitors == null) { log.warn("Cannot update MonitorTransmitters with new client connection status for session " + sessionId + " -- that session does not exist!"); return; } for (int i = 0; i < monitors.size(); i++) { MonitorTransmitter ui = (MonitorTransmitter) monitors.get(i); try { ui.connectClient(id, name); if (numConnected == 1) ui.setStartExpButtonEnabled(true); if (allConnected) ui.setAllConnected(true); } catch (MonitorDisconnectedException e) { log.error( "Failed to establish connection with MonitorTransmitter -- disconnecting from failed monitor"); disconnectMonitor(sessionId, ui); } } } /** Update the status string on each of the server monitors associated with the given session ID to the * given string */ public void updateExpStatus(int sessionId, String msg) { Vector monitors = getMonitors(sessionId); if (monitors == null) { log.warn("Cannot update MonitorTransmitters with new experiment status for session " + sessionId + " -- that session does not exist!"); return; } for (int i = 0; i < monitors.size(); i++) { MonitorTransmitter ui = (MonitorTransmitter) monitors.get(i); try { ui.updateExpStatus(msg); } catch (MonitorDisconnectedException e) { log.error( "Failed to establish connection with MonitorTransmitter -- disconnecting from failed monitor"); disconnectMonitor(sessionId, ui); } } } /** Update the ExpMonitors to set the securities and construct the info panel. This is called * as each period is initialized */ public void updatePeriodStatus(int sessionId, Vector securities) { Vector monitors = getMonitors(sessionId); if (monitors == null) { log.warn("Cannot update ExpMonitor with new period status for session " + sessionId + " -- that session does not exist!"); return; } for (int i = 0; i < monitors.size(); i++) { MonitorTransmitter ui = (MonitorTransmitter) monitors.get(i); try { ui.insertPriceChart(securities); ui.constructInfoPeriodPanel(); } catch (MonitorDisconnectedException e) { log.error( "Failed to establish connection with MonitorTransmitter -- disconnecting from failed monitor"); disconnectMonitor(sessionId, ui); } } } /** Update the time left on the server monitors of the given session with the given amount */ public void updateTimeLeft(int sessionId, int timeLeft) { Vector monitors = getMonitors(sessionId); if (monitors == null) { log.warn("Cannot update MonitorTransmitters with new time left status for session " + sessionId + " -- that session does not exist!"); return; } for (int i = 0; i < monitors.size(); i++) { MonitorTransmitter ui = (MonitorTransmitter) monitors.get(i); try { ui.setTimeLeft(timeLeft); } catch (MonitorDisconnectedException e) { log.error( "Failed to establish connection with MonitorTransmitter -- disconnecting from failed monitor"); disconnectMonitor(sessionId, ui); } } } /** Update the connection status of the given client in the given session. This takes client ID * number, not database ID number */ public void updateConnectionStatus(int sessionId, int subjectId, boolean connected) { Vector monitors = getMonitors(sessionId); if (monitors == null) { log.warn("Cannot update MonitorTransmitters with new connection status for session " + sessionId + " -- that session does not exist!"); return; } for (int i = 0; i < monitors.size(); i++) { MonitorTransmitter ui = (MonitorTransmitter) monitors.get(i); try { ui.setConnected(subjectId, connected); } catch (MonitorDisconnectedException e) { log.error( "Failed to establish connection with MonitorTransmitter -- disconnecting from failed monitor"); disconnectMonitor(sessionId, ui); } } } /** Set the start button, end button, and status message to the session running or not running state for * each monitor associated with the given session id */ public void setSessionRunning(int sessionId, boolean running) { Vector monitors = getMonitors(sessionId); if (monitors == null) { log.warn("Cannot update MonitorTransmitters with start button status for session " + sessionId + " -- that session does not exist!"); return; } for (int i = 0; i < monitors.size(); i++) { MonitorTransmitter ui = (MonitorTransmitter) monitors.get(i); try { ui.setStartExpButtonEnabled(!running); ui.setStopExpButtonEnabled(running); } catch (MonitorDisconnectedException e) { log.error( "Failed to establish connection with MonitorTransmitter -- disconnecting from failed monitor"); disconnectMonitor(sessionId, ui); } } } /** Process a basket of metrics, price chart, numoffers, and backlog updates for the monitors. * This operates simply by adding the offers contained in the UpdateBasket to the update queues * contained here, which are continually dequeued through the updater threads */ public void processTransactionUpdates(UpdateBasket basket) { priceChartUpdates.addAll(basket.getPriceChartUpdates()); metricsUpdates.addAll(basket.getMetricsUpdates()); numOffersUpdates.addAll(basket.getNumOffersUpdates()); offerBacklogUpdates.addAll(basket.getOfferBacklogUpdates()); } /** Start a thread that continually checks for new metrics updates and sends them to * the server admin screen. This thread is needed so that calls to the UI, which are * slow remote calls, do not prolong transaction timing so that transactions can * release market locks ASAP. The dispatcher can add updates to the metricsUpdates * queue */ private void startMetricsUpdateThread() { metricsUpdates = new Vector(); Runnable updater = new Runnable() { public void run() { try { while (!metricsUpdateDone) { MetricsUpdate update = getNextUpdate(); if (update.sessionId == -1) { log.debug("Shutting down auxiliary metrics update thread"); break; } Vector monitors = getMonitors(update.sessionId); if (monitors == null) { log.warn("Cannot update MonitorTransmitters with metrics information for session " + update.sessionId + " -- that session does not exist!"); continue; } for (int i = 0; i < monitors.size(); i++) { MonitorTransmitter ui = (MonitorTransmitter) monitors.get(i); try { ui.updateMetrics(update.iterations, update.numTrans, update.average); } catch (MonitorDisconnectedException e) { log.error( "Failed to establish connection with MonitorTransmitter -- disconnecting from failed monitor"); disconnectMonitor(update.sessionId, ui); } } } } catch (Exception e) { log.error("MonitorServ failed to update admin screen with metrics information", e); } } private synchronized MetricsUpdate getNextUpdate() throws InterruptedException { try { if (metricsUpdateDone) { log.info("metricsUpdate ending... returning from getNextUpdate()..."); return new MetricsUpdate(0, 0, 0, 0.f); } if (!metricsUpdates.isEmpty() && metricsUpdates.size() > 0) return (MetricsUpdate) metricsUpdates.remove(0); else wait(1000); return getNextUpdate(); } catch (Exception e) { log.debug("Metrics updates lost synchronization -- resynchronizing"); return getNextUpdate(); } } }; Thread updateThr = new Thread(updater); updateThr.setDaemon(true); updateThr.start(); } /** Works like MetricsUpdates, except for Price Chart updates */ private void startPriceChartUpdateThread() { priceChartUpdates = new Vector(); Runnable updater = new Runnable() { public void run() { try { while (!priceChartDone) { PriceChartUpdate pupdate = getNextUpdate(); if (pupdate.sessionId == -1) { log.debug("Shutting down auxiliary price chart update thread"); break; } Vector monitors = getMonitors(pupdate.sessionId); if (monitors == null) { log.warn("Cannot update MonitorTransmitters with price chart information for session " + pupdate.sessionId + " -- that session does not exist!"); continue; } for (int i = 0; i < monitors.size(); i++) { MonitorTransmitter ui = (MonitorTransmitter) monitors.get(i); try { ui.updatePriceChart(pupdate.security, pupdate.time, pupdate.price); } catch (MonitorDisconnectedException e) { log.error( "Failed to establish connection with MonitorTransmitter -- disconnecting from failed monitor"); disconnectMonitor(pupdate.sessionId, ui); } } } } catch (Exception e) { log.error("MonitorServ failed to update admin screen with price chart information", e); } } private synchronized PriceChartUpdate getNextUpdate() throws InterruptedException { try { if (priceChartDone) { log.info("priceChartUpdate ending... returning from getNextUpdate()..."); return new PriceChartUpdate(0, "0", 0.f, 0.f); } if (!priceChartUpdates.isEmpty() && priceChartUpdates.size() > 0) return (PriceChartUpdate) priceChartUpdates.remove(0); else wait(1000); return getNextUpdate(); } catch (Exception e) { log.debug("Price chart updates lost synchronization -- resynchronizing"); return getNextUpdate(); } } }; Thread updateThr = new Thread(updater); updateThr.setDaemon(true); updateThr.start(); } /** Works like MetricsUpdates, except for num offers updates */ private void startNumOffersUpdateThread() { numOffersUpdates = new Vector(); Runnable updater = new Runnable() { public void run() { try { while (!numOffersDone) { NumOffersUpdate update = getNextUpdate(); if (update.sessionId == -1) { log.debug("Shutting down auxiliary num offers update thread"); break; } Vector monitors = getMonitors(update.sessionId); if (monitors == null) { log.warn("Cannot update MonitorTransmitters with num offers information for session " + update.sessionId + " -- that session does not exist!"); continue; } for (int i = 0; i < monitors.size(); i++) { MonitorTransmitter ui = (MonitorTransmitter) monitors.get(i); try { ui.updateNumOffers(update.client, update.numOffers); } catch (MonitorDisconnectedException e) { log.error( "Failed to establish connection with MonitorTransmitter -- disconnecting from failed monitor"); disconnectMonitor(update.sessionId, ui); } } } } catch (Exception e) { log.error("MonitorServ failed to update admin screen with num offers information", e); } } private synchronized NumOffersUpdate getNextUpdate() throws InterruptedException { try { if (numOffersDone) { log.info("numOffersUpdate ending... returning from getNextUpdate()..."); return new NumOffersUpdate(0, 0, 0); } if (!numOffersUpdates.isEmpty() && numOffersUpdates.size() > 0) return (NumOffersUpdate) numOffersUpdates.remove(0); else wait(1000); return getNextUpdate(); } catch (Exception e) { log.debug("Num offers updates lost synchronization -- resynchronizing"); return getNextUpdate(); } } }; Thread updateThr = new Thread(updater); updateThr.setDaemon(true); updateThr.start(); } /** Works like MetricsUpdates, except for offer backlog updates. These updates tell the server administrator * how many offers are waiting to be processed. When this number reaches some critical amount, the server * should temporarily stop accepting offers */ private void startOfferBacklogThread() { offerBacklogUpdates = new Vector(); Runnable updater = new Runnable() { public void run() { try { while (!offerBacklogDone) { OfferBacklogUpdate update = getNextUpdate(); if (update.backlog == -1) { log.debug("Shutting down auxiliary offer backlog update thread"); break; } Enumeration sessionIds = sessionMonitors.keys(); while (sessionIds.hasMoreElements()) { Integer key = (Integer) sessionIds.nextElement(); Vector monitors = (Vector) sessionMonitors.get(key); for (int i = 0; i < monitors.size(); i++) { MonitorTransmitter ui = (MonitorTransmitter) monitors.get(i); try { ui.setOfferBacklog(update.backlog, update.rejecting); } catch (MonitorDisconnectedException e) { log.error( "Failed to establish connection with MonitorTransmitter -- disconnecting from failed monitor"); disconnectMonitor(key.intValue(), ui); } } } } } catch (Exception e) { log.error("MonitorServ failed to update admin screen with offer backlog information", e); } } private synchronized OfferBacklogUpdate getNextUpdate() throws InterruptedException { try { if (offerBacklogDone) { log.info("offerBacklogUpdate ending... returning from getNextUpdate()..."); return new OfferBacklogUpdate(0, false); } if (!offerBacklogUpdates.isEmpty() && offerBacklogUpdates.size() > 0) return (OfferBacklogUpdate) offerBacklogUpdates.remove(0); else wait(1000); return getNextUpdate(); } catch (Exception e) { log.debug("Offer backlog updates lost synchronization -- resynchronizing"); return getNextUpdate(); } } }; Thread updateThr = new Thread(updater); updateThr.setDaemon(true); updateThr.start(); } /** Log the given message onto the MonitorTransmitters of all sessions */ public void logMessage(String logStr, int level, String className) { Enumeration sessionIds = sessionMonitors.keys(); while (sessionIds.hasMoreElements()) { Integer key = (Integer) sessionIds.nextElement(); Vector monitors = (Vector) sessionMonitors.get(key); for (int i = 0; i < monitors.size(); i++) { MonitorTransmitter ui = (MonitorTransmitter) monitors.get(i); try { ui.addLogMessage(logStr, level, className); } catch (MonitorDisconnectedException e) { disconnectMonitor(key.intValue(), ui); log.error( "Failed to establish connection with MonitorTransmitter -- disconnecting from failed monitor"); } } } } /** Return a Vector full of the monitors for the given session. Return null if the session * does not exist */ private Vector getMonitors(int sessionId) { Vector monitors = (Vector) sessionMonitors.get(new Integer(sessionId)); return monitors; } /** Getter for property monitorProtocol. * @return Value of property monitorProtocol. * */ public int getMonitorProtocol() { return monitorProtocol; } /** Setter for property monitorProtocol. * @param monitorProtocol New value of property monitorProtocol. * */ public void setMonitorProtocol(int monitorProtocol) { this.monitorProtocol = monitorProtocol; } /** Getter for property monitorPort. * @return Value of property monitorPort. * */ public int getMonitorPort() { return monitorPort; } /** Setter for property monitorPort. * @param monitorPort New value of property monitorPort. * */ public void setMonitorPort(int monitorPort) { this.monitorPort = monitorPort; } /** Hashtable, keyed by session ID, that contains Vectors, each of which contain the MonitorTransmitter * interfaces active for that session ID */ private Hashtable sessionMonitors; /** A queue of metrics updates to be sent to the admin GUI. The main transaction threads * store metrics updates here, instead of calling the admin GUI itself, in order to * speed up the transaction. The metrics update thread dumps these onto the admin GUI */ private Vector metricsUpdates; /** Similar in function to metricsUpdates, this contains price chart updates */ private Vector priceChartUpdates; /** Similar in function to metricsUpdates, contains numOffers updates */ private Vector numOffersUpdates; /** Similar in function to metricsUpdates, contains offerBacklog updates */ private Vector offerBacklogUpdates; /** The port used to communicate with the monitors. When in RMI protocol mode, the port range * is this port + 20. When in TCP mode, this is the port on which the ServerSocket is created */ private int monitorPort; /** The protocol used to communicate with the monitors. Either TCP or RMI */ private int monitorProtocol; private volatile boolean metricsUpdateDone = false, priceChartDone = false, numOffersDone = false, offerBacklogDone = false; private static Log log = LogFactory.getLog(MonitorServ.class); }