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

Java tutorial

Introduction

Here is the source code for edu.caltechUcla.sselCassel.projects.jMarkets.server.control.UpdateServ.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
 */

/*
 * UpdateServ.java
 *
 * Created on March 18, 2004, 4:54 PM
 */

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

import java.util.*;
import edu.caltechUcla.sselCassel.projects.jMarkets.shared.data.*;
import edu.caltechUcla.sselCassel.projects.jMarkets.shared.network.*;
import edu.caltechUcla.sselCassel.projects.jMarkets.server.updates.*;
import edu.caltechUcla.sselCassel.projects.jMarkets.shared.data.model.earningstable.EarningsInfo;
import edu.caltechUcla.sselCassel.projects.jMarkets.shared.data.def.PeriodDef;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 *
 * @author  Raj Advani
 *
 * The UpdateServ is extended by either an HTTP or RMI implementation. Each time an
 * action is made by one of the clients, the updateQueue is filled with the Response
 * objects that must be sent to the client. An HTTP implementation will send out these
 * responses each time it receives an update request from the client. An RMI implementation
 * will connect directly to the Communicator on the client-side and sent the responses
 * immediately when received in the updateQueue
 *
 */
public abstract class UpdateServ implements Updater {

    /** Creates a new instance of UpdateServ */
    public UpdateServ() {
        initServ();
    }

    /** Initializes the UpdateServ for the new session. Called when the UpdateServ
     *  is instantiated */
    public void initServ() {
        updateQueues = new Hashtable();
        disconnected = new Hashtable();

        log.debug("UpdateServ has been initialized");
    }

    /** Add the given array of clients. Return true if the operation is successful */
    public boolean addClients(int[] dbIds) {
        for (int i = 0; i < dbIds.length; i++) {
            if (!addClient(dbIds[i]))
                return false;
        }
        return true;
    }

    /** Add the given array of clients. Return true if the operation is successful */
    public boolean deleteClients(int[] dbIds) {
        for (int i = 0; i < dbIds.length; i++) {
            if (!deleteClient(dbIds[i]))
                return false;
        }
        return true;
    }

    /** Add a client, with the given database subject ID, to the current UpdateServ. This
     *  is called at the beginning of a session. Return true if the operation is successful */
    public boolean addClient(int dbId) {
        Integer clientInt = new Integer(dbId);

        if (updateQueues.containsKey(clientInt)) {
            log.warn("UpdateServ already contains client " + dbId
                    + " -- a client may only be involved in one session at a time");
            return false;
        }

        updateQueues.put(clientInt, new Vector());
        disconnected.put(clientInt, Boolean.FALSE);
        return true;
    }

    /** Delete a client form the UpdateServ queues. This is called when a client drops out of
     *  a session or when a session is completed */
    public boolean deleteClient(int dbId) {
        Integer clientInt = new Integer(dbId);

        if (updateQueues.containsKey(clientInt)) {
            log.debug("Deleting client " + dbId + " from the UpdateServ");
            updateQueues.remove(clientInt);
            disconnected.remove(clientInt);
            return true;
        }

        log.warn("UpdateServ cannot delete client " + dbId + " -- client was not in any session!");
        return false;
    }

    /** Adds the given update response to the given client's queue */
    public void update(int dbId, Response res) {
        processUpdate(dbId, res);
    }

    /** Add the given update to the queue of the given clients */
    public void update(int[] recipients, Response update) {
        for (int i = 0; i < recipients.length; i++) {
            update(recipients[i], update);
        }
    }

    /** Send a NEW_PERIOD_UPDATE to each of the clients in the recipients array. Form the update from the given market, subject,
     *  and earnings information. Return the array of Response objects that are to be sent to the clients. */
    public Response[] sendNewPeriodUpdate(int[] recipients, int periodNum, PeriodDef period,
            EarningsInfo[] earningsHistory, Trader[] traders, OfferBook offerBook) {
        Response[] updates = new Response[recipients.length];

        for (int i = 0; i < recipients.length; i++) {
            updates[i] = new Response(Response.NEW_PERIOD_UPDATE);
            updates[i].addInfo("marketInfo", period.getMarketInfo());
            updates[i].addInfo("trader", traders[i]);
            updates[i].addInfo("earningsInfo", earningsHistory[i]);
            updates[i].addIntInfo("periodLength", period.getPeriodLength());
            updates[i].addIntInfo("periodNum", periodNum);
            updates[i].addInfo("offerBook", offerBook);
            updates[i].addStringInfo("marketEngine", period.getMarketEngine());

            update(recipients[i], updates[i]);
        }

        log.debug("UpdateServ has sent a NEW_PERIOD_UPDATE to the clients");

        return updates;
    }

    /** Send a time update to the given clients. These updates re-sync the client clocks with the
     *  server clocks. The JMTimer class determines when it is appropriate to send these re-syncs.
     *  Re-syncs also occur whenever any client sends an offer */
    public Response sendTimeUpdate(int[] recipients, int periodNum, int openDelay, int periodLength,
            int[] marketLength) {
        log.debug("Sending time update with period time " + periodLength + " and opening time " + openDelay);

        Response timeUpdate = new Response(Response.TIME_UPDATE_RESPONSE);
        timeUpdate.addIntInfo("period_time", periodLength);
        timeUpdate.addIntInfo("opening_time", openDelay);
        timeUpdate.addInfo("market_time", marketLength);
        timeUpdate.addIntInfo("periodNum", periodNum);

        update(recipients, timeUpdate);
        return timeUpdate;
    }

    /** Send an update to the clients indicating that the given market has closed */
    public Response sendMarketCloseUpdate(int[] recipients, int periodNum, int market) {
        Response marketCloseUpdate = new Response(Response.CLOSE_MARKET_UPDATE);
        marketCloseUpdate.addIntInfo("marketNum", market);
        marketCloseUpdate.addIntInfo("periodNum", periodNum);

        update(recipients, marketCloseUpdate);
        return marketCloseUpdate;
    }

    /** Send an END_PERIOD_UPDATE to each of the clients that tells them their payoffs earned.
     *  Also tell them whether to await a new period or terminate the experiment */
    public Response[] sendEndPeriodUpdate(int[] recipients, int periodNum, float[] payoffs, String[] masks,
            EarningsInfo[] earningsHistory, boolean endExperiment) {
        Response[] updates = new Response[recipients.length];

        for (int i = 0; i < recipients.length; i++) {
            updates[i] = new Response(Response.END_PERIOD_UPDATE);
            updates[i].addFloatInfo("payoff", payoffs[i]);
            updates[i].addBooleanInfo("endExperiment", endExperiment);
            updates[i].addIntInfo("periodNum", periodNum);
            updates[i].addInfo("earningsHistory", earningsHistory[i]);

            if (masks[i] != null)
                updates[i].addStringInfo("mask", masks[i]);

            update(recipients[i], updates[i]);
            log.debug("Server has sent an end of period update to client " + recipients[i]);
        }
        return updates;
    }

    /** Process a Vector of TradeUpdate objects. Send out both the invalid updates and
     *  the valid offer book updates. Return an array of Responses, one corresponding
     *  to each tradeUpdate object */
    public Response[] sendTradeUpdates(int periodNum, Vector tradeUpdates) {
        Response[] responses = new Response[tradeUpdates.size()];

        for (int i = 0; i < tradeUpdates.size(); i++) {
            TradeUpdate tupdate = (TradeUpdate) tradeUpdates.get(i);
            int type = tupdate.getType();
            int client = tupdate.getClient();

            if (type == TradeUpdate.INVALID_OFFER_UPDATE) {
                String msg = tupdate.getErrorMsg();
                String code = tupdate.getCode();

                responses[i] = sendInvalidOfferUpdate(client, periodNum, msg, code);
            } else {
                responses[i] = sendOfferBookUpdate(client, tupdate);
            }
        }
        return responses;
    }

    /** Send an OFFER_INVALID_UPDATE to the given player. This is sent to players who send
     *  in invalid offers */
    private Response sendInvalidOfferUpdate(int dbId, int periodNum, String message, String code) {
        Response update = new Response(Response.OFFER_INVALID_UPDATE);
        update.addStringInfo("message", message);
        update.addStringInfo("code", code);
        update.addIntInfo("periodNum", periodNum);

        update(dbId, update);
        return update;
    }

    /** Send an OFFER_BOOK_UPDATE to the given player */
    private Response sendOfferBookUpdate(int dbId, TradeUpdate tupdate) {
        Response update = new Response(Response.OFFER_BOOK_UPDATE);

        update.addIntInfo("action", tupdate.getAction());
        update.addIntInfo("marketId", tupdate.getMarketId());
        update.addIntInfo("priceId", tupdate.getExecutedPriceId());
        update.addIntInfo("standingPriceId", tupdate.getStandingPriceId());
        update.addLongInfo("time", tupdate.getTime());
        update.addLongInfo("key", tupdate.getKey());
        update.addStringInfo("code", tupdate.getCode());
        update.addInfo("pricebook", tupdate.getPriceBook());
        update.addIntInfo("periodNum", tupdate.getPeriodNum());

        if (tupdate.getType() == TradeUpdate.TRANSACTION_UPDATE) {
            update.addBooleanInfo("transaction", true);
            update.addInfo("numPurchases", tupdate.getNumPurchases());
            update.addInfo("numSales", tupdate.getNumSales());

        } else {
            update.addBooleanInfo("transaction", false);
        }
        update.addFloatInfo("cashHoldings", tupdate.getCashHoldings());
        update.addIntInfo("securityHoldings", tupdate.getSecurityHoldings());
        update.addStringInfo("actionType", tupdate.getTxnActionType());
        update.addInfo("unitsTraded", tupdate.getNumUnitsInTrade());

        update.addIntInfo("period_time", tupdate.getPeriodTime());
        update.addIntInfo("opening_time", tupdate.getOpeningTime());
        update.addInfo("market_time", tupdate.getMarketTime());

        update(dbId, update);
        return update;
    }

    /** Dump all updates in the queue of the given client. Typically called after receiving a
     *  re-authentication request, because old updates, collected while the client was
     *  disconnected, are not needed to bring the client back up to speed */
    protected boolean dumpUpdates(int dbId) {
        Integer clientInt = new Integer(dbId);

        if (!updateQueues.containsKey(clientInt)) {
            log.warn("Cannot dump updates for client " + dbId + " -- this client is not in any session!");
            return false;
        }

        Vector queue = (Vector) updateQueues.get(clientInt);
        updateQueues.put(clientInt, new Vector());

        return true;
    }

    /** Called by implementation of this class to directly add an update to the queue of the
     *  given client. Returns -1 if the operation failed. Ohterwise returns the new size of
     *  the queue */
    protected int enqueueUpdate(int dbId, Response update) {
        Integer clientInt = new Integer(dbId);

        if (!updateQueues.containsKey(clientInt)) {
            log.warn("Cannot enqueue update for client " + dbId + " -- this client is not in any session!");
            return -1;
        }

        Vector queue = (Vector) updateQueues.get(clientInt);
        queue.add(update);

        return queue.size();
    }

    /** Retrieves the next update in the queue of the given client. Returns a NO_UPDATE_RESPONSE if there
     *  are no updates in the given client's queue. The Response that is returned is removed permanently
     *  from the queue */
    protected Response dequeueUpdate(int dbId) {
        Integer clientInt = new Integer(dbId);

        if (!updateQueues.containsKey(clientInt)) {
            log.warn("Cannot dequeue update for client " + dbId + " -- this client is not in any session!");
            return new Response(Response.INVALID_SESSION_RESPONSE);
        }

        Vector queue = (Vector) updateQueues.get(clientInt);

        if (queue.size() < 1)
            return new Response(Response.NO_UPDATE_RESPONSE);
        else {
            Response res = (Response) queue.remove(0);
            return (Response) res;
        }
    }

    /** Given an UPDATE_REQUEST from a client, return a response containing the update to send the
     *  client */
    public Response getUpdate(Request req) {
        try {
            if (req.getType() == Request.UPDATE_REQUEST)
                return processUpdateRequest(req);
            else if (req.getType() == Request.RETRY_UPDATE_REQUEST)
                return processRetryRequest(req);

        } catch (Exception e) {
            log.error("Failed to process an update request from client", e);
            return new Response(Response.GENERAL_ACK_RESPONSE);
        }
        return new Response(Response.GENERAL_ACK_RESPONSE);
    }

    /** Set the given client to connected status */
    protected boolean setConnected(int dbId) {
        try {
            Integer clientInt = new Integer(dbId);
            if (!disconnected.containsKey(clientInt)) {
                log.warn("Cannot update connection status of client " + dbId
                        + " -- client is not connected to any session!");
                return false;
            }

            Boolean ds = (Boolean) disconnected.get(clientInt);

            if (ds.booleanValue()) {
                disconnected.put(clientInt, Boolean.FALSE);
            }

            return true;
        } catch (Exception e) {
            log.error("UpdateServ failed to update admin monitor with client " + dbId + " connection status");
        }
        return false;
    }

    /** Set the given client to disconnected status */
    protected boolean setDisconnected(int dbId) {
        try {
            Integer clientInt = new Integer(dbId);
            if (!disconnected.containsKey(clientInt)) {
                log.warn("Cannot update connection status of client " + dbId
                        + " -- client is not connected to any session!");
                return false;
            }

            Boolean ds = (Boolean) disconnected.get(clientInt);

            if (!ds.booleanValue()) {
                disconnected.put(clientInt, Boolean.TRUE);
            }

            return true;
        } catch (Exception e) {
            log.error("UpdateServ failed to update admin monitor with client " + dbId + " connection status");
        }
        return false;
    }

    /** Returns the hashtable that maps subject ID to their disconnected status. This is 
     *  requested by the dispatcher upon any re-authentication attempt */
    public Hashtable getDisconnectStatus() {
        return disconnected;
    }

    /** This abstract function is called each time an UPDATE_REQUEST is received. HTTP implementation
     *  should fill out this method */
    protected abstract Response processUpdateRequest(Request req);

    /** This abstract function is called each a RETRY_UPDATE_REQUEST is received. HTTP implementation
     *  should fill out this method, which returns the update last sent to the client */
    protected abstract Response processRetryRequest(Request req);

    /** This abstract function is called each time a client's updateQueue is updated. RMI implementation
     *  should fill out this method */
    protected abstract void processUpdate(int dbId, Response res);

    /** Keyed by dbId number, contains Vectors that contain updates ready to be sent to clients. The
     *  UpdateServ is responsible for sending these updates to the clients */
    private Hashtable updateQueues;

    /** True if dbId i is disconnected. Set to false whenever dbId i receives an update (because then we
     *  know the client is connected */
    private Hashtable disconnected;

    public static Log log = LogFactory.getLog(UpdateServ.class);
}