/*
 * @author  : Pushkala
 * @version : 1.0
 *
 * Development Environment : Oracle9i JDeveloper
 *
 * Name of the File : TradeManagementSessionFacadeBean.java
 *
 * Creation / Modification History
 *    Pushkala        26-Apr-2002        Created
 *    Elangovan       19-Aug-2003        Modified
 *
 */
package oracle.otnsamples.ibfbs.trademanagement.ejb;

// Utility Packages
import java.util.Collection;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.Date;
import java.util.Hashtable;
import java.util.List;

import java.sql.SQLException;

import java.rmi.RemoteException;

// EJB Packages
import javax.ejb.SessionContext;
import javax.ejb.CreateException;
import javax.ejb.FinderException;
import javax.ejb.RemoveException;

import javax.jms.QueueSession;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueConnection;
import javax.jms.Queue;
import javax.jms.Message;
import javax.jms.JMSException;

import javax.naming.InitialContext;

import oracle.toplink.exceptions.DatabaseException;

// Required TradeManagement Classes
import oracle.otnsamples.ibfbs.trademanagement.ejb.PortfolioLocal;
import oracle.otnsamples.ibfbs.trademanagement.ejb.PortfolioHomeLocal;
import oracle.otnsamples.ibfbs.trademanagement.ejb.TradeDetailsLocal;
import oracle.otnsamples.ibfbs.trademanagement.ejb.TradeDetailsHomeLocal;
import oracle.otnsamples.ibfbs.trademanagement.ejb.PortfolioInfo;
import oracle.otnsamples.ibfbs.trademanagement.ejb.TradeDetailsInfo;
import oracle.otnsamples.ibfbs.trademanagement.ejb.TradeHelper;
import oracle.otnsamples.ibfbs.trademanagement.ejb.PageByPage;
import oracle.otnsamples.ibfbs.toplink.Symbol;
import oracle.otnsamples.ibfbs.usermanagement.ejb.UserAccountLocal;
import oracle.otnsamples.ibfbs.usermanagement.ejb.UserAccountHomeLocal;


/**
 * This Class contains the bean implementation for the TradeManagement
 * Stateful Session EJB. This contains all the methods required for Trade
 * Management in the application. This Session Facade Bean coordinates all
 * the Trade Management Related Functionality and client invokes all this
 * functionality only through this Session Bean.
 *
 * The SessionFacadeDesign pattern is used here to provide a simple interface
 * to the complex subsystem of enterprise beans and to reduce communication
 * and dependencies between client objects and enterprise beans.
 *
 * The methods in Session Facade Bean provide the following functionality :
 * a) Getting / Setting Portfolio
 * b) Getting / Setting TradeDetails
 * c) Adding Portfolio / TradeDetails
 * d) Updating Portfolio / TradeDetails
 * e) Rollback of Portfolio / TradeDetails
 * f) Interacting with UserManagement EJBs to get User Account Information,
 *    Contact Information
 *
 * UserAccount Local EJB Object has 1:N relationship with other Local
 * Entity Beans namely PreferencesBean, AlertsBean, PortfolioBean and
 * TradeDetails Bean. This forms a wrapper to the two Entity Beans
 * PortfolioBean and TradeDetails Bean. It acts as a connecting link
 * between the TradeManagementHelper class and the entity beans.
 *  
 *  @see TradeHelper.java
 */
public class TradeManagementSessionFacadeBean implements javax.ejb.SessionBean {

  /**
   *  Instance variable to hold reference to User Account local home interface
   *  Note that using Home interfaces, we can get references to remote interfaces
   */
  private UserAccountHomeLocal  userAccountHomeLocal;

  /** Instance variable to hold reference to Trade Details local home interface */
  private TradeDetailsHomeLocal tradeDetailsHomeLocal;

  /** Instance variable to hold reference to Portfolio local home interface */
  private PortfolioHomeLocal    portfolioHomeLocal;

  /** Session Context object */
  private SessionContext sc;
  
  /** Trade Helper */
  private TradeHelper tradeHelper = null;
  
  /** Exchange Queue Connection factory instance */
  private QueueConnectionFactory exchageQConnfactory = null;;
  
  /** Exchange Queue handle */
  private Queue                  exchangeQueue       = null;

  /**
   * This is the method called by the EJB Container when
   * TradeManagementSessionFacadeBean is instantiated ie,. when the client
   * invokes the create() method on the Home interface
   * TradeManagementSessionHomeRemote. Here we call the initialize method
   * to set the objects required for TradeManagement processing.
   * @since 1.0
   */
  public void ejbCreate() {
    initialize();
  }

  /**
   * This method sets the initial context, and initializes the values of
   * references to Home Interfaces. This also sets the DataSource object.
   * @since 1.0
   */
  private void initialize() {
    try {
      InitialContext ic = new InitialContext();

      // Look up the Home interface of Entity Beans used in this Session Bean
      userAccountHomeLocal = (UserAccountHomeLocal)
                            ic.lookup("java:comp/env/ejb/UserAccountHomeLocal");
      
      tradeDetailsHomeLocal = (TradeDetailsHomeLocal)
                           ic.lookup("java:comp/env/ejb/TradeDetailsHomeLocal");

      portfolioHomeLocal = (PortfolioHomeLocal)
                              ic.lookup("java:comp/env/ejb/PortfolioHomeLocal");

      tradeHelper = TradeHelper.getInstance();

    } catch(javax.naming.NamingException ne) {
      System.err.println(" TMSFB.ejbCreate(): Error during JNDI lookup : "+ne.toString());
    } catch(Exception ex) {
      System.err.println(" TMSFB.ejbCreate(): Generic Error : "+ex.toString());
    }
  }

  /**
   * This method constructs a new Collection from the input collection,
   * since we need to send the following details to the client
   * 1) boolean value to indicate if the page need iteration
   * 2) Size of the Collection
   * 3) boolean value to indicate if the records have previous set
   * 4) boolean value to indicate if the records have next set
   * 5) Portfolio Details Collection
   * @param allRecords    Collection of all records
   * @param recordNumber  Record Number
   * @param linesPerPage  Lines per page
   * @since 1.0
   */
  private static Collection createNewCollection(Collection allRecords,
                                                String recordNumber,
                                                int linesPerPage) {
    Collection newColl = new ArrayList();

    // Size of the input collection
    int collSize = allRecords.size();

    if (recordNumber == null) {
      recordNumber = "1";
    }

    boolean needsPageIteration = false;

    // Page Iteration is required only if number of rows
    // retrieved exceeds linesPerPage
    if (collSize == 0 || collSize <= linesPerPage) {
      needsPageIteration = false;
    } else {
      needsPageIteration = true;
    }

    if (needsPageIteration) {
      PageByPage pbp = new PageByPage();
      // Get the collection before getting hasPrev, hasNext
      // since the variables are set by the getCollection method
      Collection temp = pbp.getCollection((List)allRecords,
                                          recordNumber,
                                          linesPerPage);
      newColl.add(pbp.hasPrevRecords());
      newColl.add(pbp.hasNextRecords());
      newColl.add(temp);
    } else {
      newColl.add("false");
      newColl.add("false");
      newColl.add(allRecords);
    }

    return newColl;
  }

  /**
   * This method calls the methods of the Entity Beans to get the Portfolio
   * details corresponding to the input User Account Number and returns the
   * Portfolio details as a Collection.
   *
   * @param accountNumber Account Number
   * @param recordNumber  Record Number
   * @param linesPerPage  Lines Per Page
   * @return The Collection of Portfolio Details for the input Account Number
   * @exception RemoteException If error occurs while getting the details
   * @since 1.0
   */
  public Collection getPortfolio(Integer accountNumber, String recordNumber,
                                 int linesPerPage)
      throws RemoteException {

    Collection allPortfolio = new ArrayList();
    Collection pfColl       = null;

    try {
      // Find the User Account Entity for the input account number
      UserAccountLocal userAccountLocal =
        userAccountHomeLocal.findByPrimaryKey(accountNumber);

      // Get the Collection of Portfolio corresponding to the user account
      pfColl = (Collection)userAccountLocal.getPortfolio();

      // Create an iterator for the above Collection
      Iterator portfolioLocalIter = pfColl.iterator();

      while(portfolioLocalIter.hasNext()) {

        // If the iterator has more values, get the next portfolio information
        PortfolioLocal pf = (PortfolioLocal)portfolioLocalIter.next();

        if(pf.getQuantity().intValue() > 0) {
        
          Symbol s = tradeHelper.getSymbol(pf.getSymbol());

          // If the Portfolio Quantity is greater than 0,
          // Add the Portfolio information to a new collection
          allPortfolio.add(new PortfolioInfo(pf.getLineNo(), pf.getQuantity(),
                                             pf.getPrice(), pf.getSymbol(),
                                             s.getCompanyname(),
                                             pf.getPurchaseDate(),
                                             pf.getPurchaseMode(),
                                             pf.getTradeId()));
        }
      }
    } catch(FinderException ex) {
      throw (new RemoteException("Finder Exception in get Portfolio : " +
                                 ex.toString()));
    } catch(DatabaseException ex) {
      throw (new RemoteException("SQL Exception in get Portfolio : " +
                                 ex.toString()));
    }

    //  return the new collection created
    return createNewCollection(allPortfolio, recordNumber, linesPerPage);
  }


  /**
   * This method gets the Portfolio Valuation Information for the input
   * Account Number. A User can have multiple Portfolio details, so a
   * collection of Portfolio Valuation Information is returned.
   *
   * @param accountNumber Account Number
   * @param recordNumber  Record Number
   * @param linesPerPage  Lines Per Page
   * @return The Collection of Portfolio Valuation Information for the
   *         input Account Number
   * @exception RemoteException
   * @since 1.0
   */
  public Collection getPortfolioValuation(Integer accountNumber,
                                          String recordNumber,
                                          int linesPerPage)
      throws RemoteException {

    Collection pfValue    = new ArrayList();
    Collection pfColl     = null;

    float totalAssetValue = 0; // The Total Asset Value

    try {
      // Find the User Account Entity for the input account number
      UserAccountLocal userAccountLocal =
        userAccountHomeLocal.findByPrimaryKey(accountNumber);

      // Get the Collection of Portfolio corresponding to the user account
      pfColl = (Collection)userAccountLocal.getPortfolio();

      // Create an iterator for the above Collection
      Iterator portfolioLocalIter = pfColl.iterator();

      while(portfolioLocalIter.hasNext()) {

        // If the iterator has more values, get the next portfolio information
        PortfolioLocal pf = (PortfolioLocal)portfolioLocalIter.next();

        if(pf.getQuantity().intValue() > 0) {

          // If the Portfolio Quantity is greater than 0,
          // Add the Portfolio information to a new collection
          float currPrice  = tradeHelper.getLatestRate(pf.getSymbol()).getLowprice().floatValue();
          float buyValue   = pf.getQuantity().floatValue() * pf.getPrice();
          float currValue  = pf.getQuantity().floatValue() * currPrice;
          float gainorloss = currValue - buyValue;
          float percent    = (gainorloss / buyValue) * 100;
          Symbol s = tradeHelper.getSymbol(pf.getSymbol());

          pfValue.add(new PortfolioValue(pf.getLineNo(), pf.getQuantity(),
                                         pf.getPrice(), pf.getSymbol(),
                                         s.getCompanyname(),
                                         pf.getPurchaseDate(),
                                         pf.getPurchaseMode(),
                                         pf.getTradeId(), currPrice,
                                         gainorloss, percent, currValue));

          // Compute the Total Asset Value
          totalAssetValue = totalAssetValue + currValue;
        }
      }
    } catch(FinderException ex) {
      throw (new RemoteException("Finder Exception in get Portfolio : " +
                                 ex.toString()));
    } catch(DatabaseException ex) {
      throw (new RemoteException("SQL Exception in get Portfolio : " +
                                 ex.toString()));
    }

    Collection retColl = createNewCollection(pfValue, recordNumber, linesPerPage);
    retColl.add(new Float(totalAssetValue));

    //  return the new collection created
    return retColl;
  }

  /**
   * This method calls the methods on the Entity Beans to get the Trade details
   * corresponding to the input User Account Number and returns the details
   * as a Collection.
   *
   * @param accountNumber Account Number
   * @param recordNumber  Record Number
   * @param linesPerPage  Lines Per Page
   * @return The Collection of Trade Details for the input Account Number
   * @exception RemoteException If error occurs while getting the details
   * @since 1.0
   */
  public Collection getTradeDetails(Integer accountNumber, String recordNumber,
                                    int linesPerPage)
      throws RemoteException {

    Collection allTrades = new ArrayList();
    Collection tdColl    = null;

    try {
      // Find the User Account Entity for the input account number
      UserAccountLocal userAccountLocal =
        userAccountHomeLocal.findByPrimaryKey(accountNumber);

      // Get the Collection of TradeDetails corresponding to the user account
      tdColl = (Collection)userAccountLocal.getTradeDetails();

      // Create an iterator for the above Collection
      Iterator tradesIter = tdColl.iterator();

      while(tradesIter.hasNext()) {

        // If the iterator has more values, get the next tradedetails info
        TradeDetailsLocal td = (TradeDetailsLocal)tradesIter.next();

        String action = "Sold";
        // Set the full expansion of action depending on the value
        if(td.getAction().equals("B")) {
          action = "Bought";
        } else if(td.getAction().equals("T")) {
          action = "Transferred";
        }

        // Add the TradeDetails information to a new collection
        allTrades.add(new TradeDetailsInfo(td.getTradeId(), action,
                                           td.getQuantity(), td.getTradeDate(),
                                           td.getPrice(), td.getSymbol(),
                                           td.getOrderType(), td.getStatus(),
                                           td.getExchangeId()));
      }
    } catch(FinderException ex) {
      throw (new RemoteException("Finder Exception in get trade details : " +
                                 ex.toString()));
    }

    //  return the new collection created
    return createNewCollection(allTrades, recordNumber, linesPerPage);
  }

  /**
   * This method calls the methods on the Entity Bean and returns the Trade
   * details for the input trade id as a Value Object which reduces the
   * network traffic between the client and the server.
   *
   * @param tradeId Trade Id
   * @return The Trade Details information as a Value Object TradeDetailsInfo
   * @exception RemoteException If error occurs while getting the details
   * @since 1.0
   */
  private TradeDetailsInfo getTradeDetailsInfo(Integer tradeId)
      throws RemoteException {

    TradeDetailsInfo tdInfo = null;

    try {
      // Find the Trade Details Entity for the input trade id
      TradeDetailsLocal tradeDetailsLocal =
        tradeDetailsHomeLocal.findByPrimaryKey(tradeId);

      // Create a new object with the obtained Trade Details Entity
      tdInfo = new TradeDetailsInfo(tradeDetailsLocal.getTradeId(),
                                    tradeDetailsLocal.getAction(),
                                    tradeDetailsLocal.getQuantity(),
                                    tradeDetailsLocal.getTradeDate(),
                                    tradeDetailsLocal.getPrice(),
                                    tradeDetailsLocal.getSymbol(),
                                    tradeDetailsLocal.getOrderType(),
                                    tradeDetailsLocal.getStatus(),
                                    tradeDetailsLocal.getExchangeId());
    } catch(FinderException ex) {
      throw (new RemoteException("Finder Exception in get trade details info : "
                                 + ex.toString()));
    }
    return tdInfo;
  }

  /**
   * This method calls the methods on the Entity Bean to insert a new record
   * in the PORTFOLIO table.
   *
   * @param accountNumber Account Number
   * @param pfInfo        Portfolio Information
   * @return The Line Number of the new Portfolio record created
   * @exception RemoteException If error occurs while adding Portfolio
   * @since 1.0
   */
  private Integer addPortfolio(Integer accountNumber,
                               PortfolioInfo pfInfo)
      throws RemoteException {

    Collection pfColl       = null;
    Integer    nextLineNo   = new Integer(0);
    int        rowCount     = 0;

    try {
      // Find the User Account Entity for the input account number
      UserAccountLocal userAccountLocal =
        userAccountHomeLocal.findByPrimaryKey(accountNumber);

      // Get the Collection of Portfolio corresponding to the user account
      pfColl = (Collection)userAccountLocal.getPortfolio();

      rowCount = pfColl.size();

      // Next Line Number is the next row count
      nextLineNo = new Integer(rowCount + 1);

      // get the next portfolio id
      Integer nextPortfolioId = tradeHelper.getNextID("PORTFOLIO_SEQ");
      if(nextPortfolioId.intValue() == 0) {
        throw (new CreateException("Unable to set Portfolio details, " +
                                   "PORTFOLIO_SEQ not found : "));
      }

      // Create a new Portfolio Entity
      PortfolioLocal portfolioLocal =
                       portfolioHomeLocal.create(accountNumber,
                                                 nextPortfolioId,
                                                 nextLineNo,
                                                 pfInfo.getQuantity(),
                                                 pfInfo.getPrice(),
                                                 pfInfo.getSymbol(),
                                                 pfInfo.getPurchaseDate(),
                                                 pfInfo.getPurchaseMode(),
                                                 pfInfo.getTradeId());

      // add the entity to the new collection
      pfColl.add(portfolioLocal);

      // Set the collection to the User Account Entity
      userAccountLocal.setPortfolio(pfColl);

    } catch(FinderException ex) {
      throw (new RemoteException("Finder Exception in Add Portfolio : " +
                                 ex.toString()));
    } catch(CreateException ex) {
      throw (new RemoteException("Create Exception in Add Portfolio : " +
                                 ex.toString()));
    } catch(DatabaseException ex) {
      throw (new RemoteException("SQL Exception in Add Portfolio : " +
                                 ex.toString()));
    }
    return nextLineNo;
  }

  /**
   * This method is used to update the quantity of stocks in the PORTFOLIO
   * Table once some quantity or all the stocks are sold. This method is
   * called when the user chooses to sell stocks.
   *
   * @param accountNumber Account Number
   * @param pfInfo        Portfolio Information
   * @return String indicating the success of the update operation
   * @exception RemoteException If error occurs while updating Portfolio
   * @since 1.0
   */
  private String updatePortfolio(Integer accountNumber,
                                 PortfolioInfo pfInfo)
      throws RemoteException {

    Collection allPortfolio = new ArrayList();
    Collection pfColl       = null;
    String     retValue     = "SUCCESS";
    boolean    found        = false;

    try {
      // Find the User Account Entity for the input account number
      UserAccountLocal userAccountLocal =
        userAccountHomeLocal.findByPrimaryKey(accountNumber);

      // Get the Collection of Portfolio corresponding to the user account
      pfColl = (Collection)userAccountLocal.getPortfolio();

      // Create an iterator for the above Collection
      Iterator pfIter = pfColl.iterator();

      boolean set = true;

      while (pfIter.hasNext()) {

        // If the iterator has more values, get the next portfolio information
        PortfolioLocal pf = (PortfolioLocal)pfIter.next();

        if (pf.getLineNo().equals(pfInfo.getLineNo())) {

          found = true;

          if (pf.getQuantity().intValue() >= pfInfo.getQuantity().intValue()) {

            // Update the quantity of the line number
            // which is in the input portfolio collection
            int newQty = 0;
            newQty = pf.getQuantity().intValue() -
                     pfInfo.getQuantity().intValue();

            if (newQty == 0) {
              pf.remove();
              set = false;
            } else {
              pf.setQuantity(new Integer(newQty)); // Set the new quantity
            }
          } else {
              retValue = "INSUFFICIENTQUANTITY";
          }
        }

        if (set) {
          allPortfolio.add(pf); // Add the record to the new collection
        } else {
          set = true;
        }
      }

      // Set the collection to the User Account Entity
      userAccountLocal.setPortfolio(allPortfolio);

    } catch(FinderException ex) {
      throw (new RemoteException("Finder Exception in update Portfolio : " +
                                 ex.toString()));
    } catch(RemoveException ex) {
      throw (new RemoteException("Remove Exception in update Portfolio : " +
                                 ex.toString()));
    }

    if (!found) {
      retValue = "RECORDNOTFOUND";
    }

    return retValue;
  }

  /**
   * This method is called while creating a new record in the TRADEDETAILS
   * table. This is called whenever any 'Buy', 'Sell' or 'Transfer'
   * transaction is carried out by a user. The new record is created for the
   * input user account number with the input tradedetails information.
   *
   * @param accountNumber Account Number
   * @param tdInfo        Trade Details Information
   * @return The Next Trade Id of the Trade Details record created.
   * @exception RemoteException If error occurs while adding TradeDetails
   * @since 1.0
   */
  private Integer addTradeDetails(Integer accountNumber,
                                  TradeDetailsInfo tdInfo)
      throws RemoteException {

    Collection tdColl      = null;
    Integer    nextTradeId = new Integer(0);

    try {
      // Find the User Account Entity for the input account number
      UserAccountLocal userAccountLocal =
        userAccountHomeLocal.findByPrimaryKey(accountNumber);

      // Get the Collection of TradeDetails corresponding to the user account
      tdColl = (Collection)userAccountLocal.getTradeDetails();

      // Get the next trade id number
      nextTradeId = tradeHelper.getNextID("TRADE_SEQ");
      
      Symbol s = tradeHelper.getSymbol(tdInfo.getSymbol());

      // Create a new Trade Details entity
      TradeDetailsLocal tradeDetailsLocal =
        tradeDetailsHomeLocal.create(nextTradeId, accountNumber, tdInfo.getAction(),
                                     tdInfo.getQuantity(), tdInfo.getTradeDate(),
                                     tdInfo.getPrice(), tdInfo.getSymbol(),
                                     tdInfo.getOrderType(), tdInfo.getStatus(),
                                     new Integer(s.getExchangeid().intValue()));

      // Add the Trade details Entity to the new collection
      tdColl.add(tradeDetailsLocal);

      // Set the collection to the User Account Entity
      userAccountLocal.setTradeDetails(tdColl);

    } catch(FinderException ex) {
      throw (new RemoteException("Finder Exception in Add Trade Details : " +
                                 ex.toString()));
    } catch(CreateException ex) {
      throw (new RemoteException("Create Exception in Add Trade Details : " +
                                 ex.toString()));
    } catch(DatabaseException ex) {
      throw (new RemoteException("SQL Exception in Add Trade Details : " +
                                 ex.toString()));
    }
    return nextTradeId;
  }

  /**
   * This method is used to check the available balance in the user account
   * whenever a 'Buy' or 'Sell' transaction is carried out by the user.
   *
   * @param accountNumber Account Number
   * @param amount        Amount to be added or subtracted
   * @return Boolean value indicating whether the account balance can be
   *         updated or not
   * @exception RemoteException If error occurs while checking account balance
   * @since 1.0
   */
  private boolean checkBalance(Integer accountNumber, float amount)
      throws RemoteException {

    boolean flag = false;

    try {

      // Find the User Account Entity for the input account number
      UserAccountLocal userAccountLocal =
        userAccountHomeLocal.findByPrimaryKey(accountNumber);

      // Get the account balance for the user
      float availableBalance = userAccountLocal.getAccountBalance();

      // Check if there is enough balance
      if (amount <= availableBalance) {
        flag = true;
      }

    } catch(FinderException ex) {
      throw (new RemoteException("Finder Exception in Update Balance : " +
                                 ex.toString()));
    }

    return flag;
  }

  /**
   * This method is used to update the available balance in the user account
   * whenever a 'Buy' or 'Sell' transaction is carried out by the user. If the
   * input operation is 'ADD', the input amount is added to the User's account.
   * Else if it is 'SUB', the balance is checked for availability before
   * subtracting the amount.
   *
   * @param accountNumber Account Number
   * @param amount        Amount to be added or subtracted
   * @param operation     Valid Values : 'ADD', 'SUB'
   * @return Boolean value indicating whether the account balance was
   *         updated or not
   * @exception RemoteException If error occurs while updating account balance
   * @since 1.0
   */
  private boolean checkAndUpdateBalance(Integer accountNumber,
                                        float amount,
                                        String operation)
      throws RemoteException {

    boolean flag = false;

    try {
      // Find the User Account Entity for the input account number
      UserAccountLocal userAccountLocal =
        userAccountHomeLocal.findByPrimaryKey(accountNumber);

      // Get the account balance for the user
      float availableBalance = userAccountLocal.getAccountBalance();

      if(operation.equals("SUB")) {
        // In case of subtraction, check if there is enough balance
        if(amount <= availableBalance) {
          // subtract amount
          userAccountLocal.setAccountBalance(availableBalance - amount);
          flag = true;
        }
      } else {
        if(operation.equals("ADD")) {
          // add amount
          userAccountLocal.setAccountBalance(availableBalance + amount);
          flag = true;
        }
      }
    } catch(FinderException ex) {
      throw (new RemoteException("Finder Exception in Update Balance : " +
                                 ex.toString()));
    }
    return flag;
  }

  /**
   * This method is called from TradeManagementHelper when the user buys
   * stock using the application. If the User has enough balance to carry out
   * the transaction, the trade amount is deducted from his account and
   * Portfolio Details, Trade Details are added. The Order Details are sent by
   * calling the method sendOrderDetails. If the available balance for the user
   * is not sufficient for the transaction, corresponding message is returned.
   * If everything goes through fine, 'SUCCESS' message is returned.
   * Else On 'FAILURE', corresponding message is returned.
   *
   * @param accountNo  Account Number
   * @param quantity   Quantity of stocks bought
   * @param symbol     Stock Symbol
   * @param isTraded   Boolean indicating if the symbol is traded or not
   * @param userEmail  Email id of the Logged in User
   * @param systemDate Formatted String containing value of System Date
   * @return The return message indicates whether the buy transaction
   *         was a success or failure.
   * @exception RemoteException If error occurs while adding details
   * @exception SQLException    If error occurs while getting data
   * @since 1.0
   */
  public String buyStock(Integer accountNo, Integer quantity,
                         String symbol, boolean isTraded,
                         String userEmail, Hashtable connHash, String systemDate)
      throws RemoteException, SQLException {

    String retMessage = "FAILURE";

    float price = tradeHelper.getLatestRate(symbol).getLowprice().floatValue();

    // Additional check though the check exists in Helper class itself
    if (price == 0) {
      retMessage = "Invalid Symbol, Unable to get the Stock Rate for the Symbol";
      return retMessage;
    }

    float amount       = quantity.floatValue() * price; // Calculate the trade amount

    Date  sysDate      = new Date();  // Current Date
    boolean updateFlag = false;

    // The following steps are involved in the buy transaction
    // 1) If the User's Account Balance is sufficient for the trade,
    //    the trade amount is deducted from the available balance
    //    and further trade processing is carried out as in Step 2)
    //    Else the corresponding error message is returned
    // 2) TradeDetails, Portfolio record is created with the stock information
    // 3) The Order Details are sent for further processing
    // 4) If the send is successful, the buy transaction is complete
    //    and 'SUCCESS' message is returned, Else the corresponding
    //    error message is returned.

    // The buy transaction is not complete until all the steps are carried
    // out successfully. Hence if any exception occurs during the transaction,
    // the previous steps have to be rolled back. This is achieved using the
    // setRollbackOnly() method on the SessionContext. This ensures that in
    // case of exception or failure, no changes are committed to the database.

    try {
      // Check the available balance, the return flag indicates
      // whether the balance can be updated or not
      updateFlag = checkBalance(accountNo, amount);
    } catch(RemoteException re) {
      throw new RemoteException("Remote Exception while checking balance : " +
                                re.toString());
    }

    int retVal;

    if(updateFlag) { // If the available balance permitted the transaction

      Integer nextTradeId = new Integer(0);

      // Create TradeDetailsInfo object
      TradeDetailsInfo tdInfo = new TradeDetailsInfo(null, "B", quantity,
                                                     sysDate, price, symbol,
                                                     "M", "O", null);
      try {

        // Add new trade details info for the account number and return the trade id
        nextTradeId = addTradeDetails(accountNo, tdInfo);

      } catch(RemoteException re) {
        sc.setRollbackOnly();
        throw new RemoteException("Remote Exception while adding tradedetails: "
                                  + re.toString());
      } catch(Exception ex) {
        sc.setRollbackOnly();
        throw new RemoteException("Exception while adding tradedetails: " +
                                  ex.toString());
      }

      Integer nextId = new Integer(0);

      // If the trade id has a valid value
      if(nextTradeId.intValue() > 0) {

        // Create PortfolioInfo object
        PortfolioInfo pfInfo = new PortfolioInfo(null, quantity, price,
                                                 symbol, null, sysDate,
                                                 "P", nextTradeId);
        try {

          // Add new portfolio info for the account number and return the next id
          nextId = addPortfolio(accountNo, pfInfo);

        } catch(RemoteException re) {
          sc.setRollbackOnly();
          throw new RemoteException("Remote Exception while adding Portfolio : "
                                    + re.toString());
        }

        try {

          // The Order Details are sent to the Exchange.
          // The Trade is successful only if the send is successful
          retVal = sendOrderDetails(accountNo, symbol, "B",
                                    quantity, isTraded, userEmail,
                                    connHash, systemDate);
          if (retVal < 0) {
            sc.setRollbackOnly();
            retMessage = "Failed to contact another FBS " +
                         "while trying to buy the stock";
          }

        } catch(RemoteException re) {
          retVal = -1;
          sc.setRollbackOnly();
          throw new RemoteException("Remote Exception while adding Portfolio : "
                                    + re.toString());
        }

        if (retVal >= 0) {

          boolean checkFlag = checkAndUpdateBalance(accountNo, amount, "SUB");

          // Check if available balance permits the transaction to complete
          // Return flag indicates whether the balance was updated or not
          if (checkFlag) {

            retMessage = "SUCCESS";

          } else {

            retMessage = "Cannot Proceed with the transaction, " +
                         "Not enough Account Balance";
            sc.setRollbackOnly();

          }
        }
      }

      if (retMessage.equals("FAILURE")) {

        retMessage = "Failed to carry out the transactions";

      }

    } else {

      retMessage = "Cannot Proceed with the transaction, " +
                   "Not enough Account Balance";
    }

    return retMessage;
  }

  /**
   * This method is called by TradeManagementHelper and is used by the
   * AdminHelper to transfer stocks from a corporate to an individual.
   * Portfolio details and Trade Details are added.
   * The Order Details are sent by calling the method sendOrderDetails.
   *
   * @param toAccountNumber Account Number to which Stocks are
   *                           to be transferred
   * @param symbol       Stock Symbol
   * @param qty          Quantity
   * @param price        Price
   * @param isTraded     Boolean indicating if the symbol
   *                     is traded or not
   * @param userEmail    Email id of the Logged in User
   * @param systemDate   Formatted String containing value of System Date
   * @return An integer indicating success or failure,
   *         0 indicates success and -1 indicates failure
   * @exception RemoteException If error occurs while adding details
   * @since 1.0
   */
  public Integer corporateTransfer(Integer toAccountNumber, String symbol,
                                   Integer qty, Float price, boolean isTraded,
                                   String userEmail, Hashtable connHash,
                                   String systemDate)
      throws RemoteException {

    Integer nextTradeId = new Integer(0);
    int     retVal      = 0;

    try {

      Date  sysDate      = new Date();  // Current Date

      // Calculate the trade amount
      float amount = qty.intValue() * price.floatValue();

      // The corporate transfer process involves the following steps :
      // 1) New TradeDetails and Portfolio are created
      // 2) The Order Details are sent for further processing
      // 3) If the send is successful, the transaction is complete and
      //    '0' is returned indicating success, Else -1 is returned.

      // The corporate transfer transaction is not complete until all the steps
      // are carried out successfully. Hence if any exception occurs during the
      // transaction, the previous steps have to be rolled back. This is achieved
      // using the setRollbackOnly() method on the SessionContext. So in case of
      // exception or failure, the transactions are rolled back.

      // Create TradeDetailsInfo object
      TradeDetailsInfo tdInfo = new TradeDetailsInfo(null, "T", qty, sysDate,
                                                     price.floatValue(), symbol,
                                                     "M", "O", null);

      // Add new trade details info for the account number
      // and return the next trade id
      nextTradeId = addTradeDetails(toAccountNumber, tdInfo);

      // Create PortfolioInfo object
      PortfolioInfo pfInfo = new PortfolioInfo(null, qty, price.floatValue(),
                                               symbol, null, sysDate, "P",
                                               nextTradeId);
      Integer nextId;

      // Add new portfolio info for the account number and return the next id
      nextId = addPortfolio(toAccountNumber, pfInfo);

      // The Order Details are sent to the Exchange.
      // The Trade is successful only if the send is successful
      retVal = sendOrderDetails(toAccountNumber, symbol, "T", qty, isTraded,
                                userEmail, connHash, systemDate);

      if (retVal < 0) {
        sc.setRollbackOnly();
        nextTradeId = new Integer(0);
      }

    } catch(RemoteException ex) {
      throw new RemoteException("Exception in corporate transfer " +
                                "while sending order details : \n" +
                                ex.toString());
    }

    return nextTradeId;
  }

  /**
   * This method is called from TradeManagementHelper when the user sells stock
   * using the application. The trade amount is calculated after getting the
   * current stock price. Trade Details are added. Portfolio details are updated.
   * The User's Available balance is updated depending on whether the stock was
   * bought by the user or obtained through corporate transfer. In case of
   * corporate transfer only the difference between the current stock value and
   * the stock value when it was transferred (ie,. the profit) is updated.
   * In case the profit is negative, the balance remains unchanged.
   * Order Details are sent by calling the method sendOrderDetails.
   *
   * @param accountNo    User Account Number
   * @param qtyValues    String Array of Stock Quantity Values
   * @param lineNos      String Array of Line Numbers
   * @param symbols      String Array of Stock Symbols
   * @param tradeId      String Array of Trade Id
   * @param isTradedFlag String Array containing 'Y' if the symbol is traded
   *                        or 'N' if not
   * @param userEmail    Email id of the Logged in User
   * @param systemDate   Formatted String containing value of System Date
   * @return The return message is a string which contains details
   *         about the failure or success of the sell transactions.
   * @exception RemoteException If error occurs while adding details
   * @exception SQLException    If error occurs while getting data
   * @since 1.0
   */
  public String sellStock(Integer accountNo, String[] qtyValues,
                          String[] lineNos, String[] symbols,
                          String[] tradeId, String[] isTradedFlag,
                          String userEmail, Hashtable connHash,
                          String systemDate)
      throws RemoteException, SQLException {

    Integer      quantity   = new Integer(0);
    int          retVal     = 0;
    String       retMessage = "";
    StringBuffer sb         = new StringBuffer();

    Date  sysDate      = new Date();  // Current Date

    for (int i = 0;i < lineNos.length;i++) {

      retMessage = "FAILURE";

      if (!qtyValues[i].equals("")) {

        Integer nextTradeId = new Integer(0);
        float price  = tradeHelper.getLatestRate(symbols[i]).getLowprice().floatValue();

        try {

          quantity = new Integer(qtyValues[i]);

        } catch (NumberFormatException nfe) {

          retMessage = "INVALIDQUANTITY";
          sb.append("Failed to sell stocks of '");
          sb.append(symbols[i]);
          sb.append("' : Invalid Quantity! \n");

        }

        String           retValue = "FAILURE";
        TradeDetailsInfo tdInfo   = null;

        // The following steps are involved in the sell transaction
        // 1) TradeDetails, Portfolio record is created with the stock information
        // 2) The User's Account Balance is updated with the trade amount
        // 3) The Order Details are sent for further processing
        // 4) If the send is successful, the sell transaction is complete,
        //    and 'SUCCESS' message is appended to the return string,
        //    Else the corresponding error message is appended.
        // 5) The above steps are repeated for all the records.

        // The sell transaction is not complete until all the steps are
        // carried out successfully. Hence if any exception occurs during
        // the transaction, the previous steps have to be rolled back.
        // This is achieved using the setRollbackOnly() method on the SessionContext.
        // So in case of exception or failure, the transactions are rolled back.

        if (!(retMessage.equals("INVALIDQUANTITY"))) {
          // Proceed further only if the quantity is valid

          // Create TradeDetailsInfo object
          tdInfo = new TradeDetailsInfo(null, "S", quantity,
                                        sysDate, price,
                                        symbols[i], "M",
                                        "O", null);
          try {

            // Add new trade details info for the account number
            // and return the trade id
            nextTradeId = addTradeDetails(accountNo, tdInfo);

          } catch(RemoteException re) {
            sc.setRollbackOnly();
            throw new RemoteException("Remote Exception while adding " +
                                      "tradedetails sellStock: " +
                                      re.toString());
          }

          // Create PortfolioInfo object
          PortfolioInfo pfInfo = new PortfolioInfo(Integer.valueOf(lineNos[i]),
                                                   quantity, price, symbols[i],
                                                   null, sysDate, "P", nextTradeId);

          // If the trade id has a valid value
          if (nextTradeId.intValue() > 0) {

            try {

              // Update portfolio info for the Account Number
              retValue = updatePortfolio(accountNo, pfInfo);

            } catch(RemoteException re) {
              sc.setRollbackOnly();
              throw new RemoteException("Remote Exception while updating " +
                                        "Portfolio details sellStock: " +
                                        re.toString());
            }

            if (retValue.equals("INSUFFICIENTQUANTITY") ||
                retValue.equals("RECORDNOTFOUND")) {
              sc.setRollbackOnly();
              sb.append("Failed to sell stocks of '");
              sb.append(symbols[i]);
              sb.append("' : Stocks possibly sold in another transaction! \n");
            }
          }

        }

        if (retValue.equals("SUCCESS")) {

          boolean updateFlag = false;
          float amount       = 0;
          tdInfo             = null;

          try {

            // Get the TradeDetailsInfo corresponding to the stock which is being sold
            tdInfo = getTradeDetailsInfo(new Integer(tradeId[i]));

          } catch(RemoteException re) {
            sc.setRollbackOnly();
            throw new RemoteException("Remote Exception while getting TradeDetails Info : "
                                      + re.toString());
          }

          // Calculate amount depending on whether the stocks were purchased or
          // transferred through corporate transfer
          // In case of purchase, amount = (quantity * current price)
          // In case of corporate transfer,
          //   amount = (quantity * (current price - buy price)
          if (tdInfo.getAction().equals("B")) {

            amount = quantity.floatValue() * price;

          } else if(tdInfo.getAction().equals("T")) {

            // Stocks transferred through corporate transfer
            amount = quantity.floatValue() * (price - tdInfo.getPrice());

            // Only profit has to be considered
            if (amount < 0) {
              amount = 0;
            }
          }

          try {

            // Check the available balance and add the trade amount
            // Return flag indicates whether the balance was updated or not
            updateFlag = checkAndUpdateBalance(accountNo, amount, "ADD");

          } catch(RemoteException re) {
            sc.setRollbackOnly();
            throw new RemoteException("Remote Exception while updating balance : "
                                      + re.toString());
          }

          boolean isTraded = false;
          if (isTradedFlag[i].equals("Y")) {
            isTraded = true;
          }

          // The Order Details are sent to the Exchange.
          // The Trade is successful only if the send is successful
          retVal = sendOrderDetails(accountNo, symbols[i], "S", quantity,
                                    isTraded, userEmail, connHash, systemDate);

          if (retVal < 0) {
            // If send is not successful, the transaction has to be rolled back
            sc.setRollbackOnly();
          } else {
            retMessage = "SUCCESS";
          }

          if(retMessage.equals("SUCCESS")) {
            sb.append("Transaction completed. ");
            sb.append(qtyValues[i]);
            sb.append(" stocks of '");
            sb.append(symbols[i]);
            sb.append("' sold at the price : $");
            sb.append(price);
            sb.append(" per share \n");
          } else {
            sb.append("Failed to sell stocks of '");
            sb.append(symbols[i]);
            sb.append("' \n");
          }
        }
      }
    }
    return sb.toString();
  }

  /**
   * This method is called when any user transaction ie,. 'Buy', 'Sell' or
   * 'Transfer' happens. The trade details are sent to the stock exchange. The
   * Stock exchange is a JMS queue which provides asynchronous processing for
   * conducting trade. The exchange process the trade request and sends a 
   * notification to the user.
   *
   * @param accountNo User Account Number
   * @param symbol    Stock Symbol
   * @param tradeType Trade Type
   *        Valid Values : 'B' for 'Buy', 'S' for 'Sell', 'T' for 'Transfer'
   * @param quantity  Quantity
   * @return Returns an int indicating success or failure :
   *         0 for success and -1 for failure
   * @exception RemoteException If error occurs while sending the Order Details
   * @since 1.0
   */
  private int sendOrderDetails(Integer accountNo, String symbol,
                               String tradeType, Integer quantity,
                               boolean isTraded, String userEmail,
                               Hashtable connHash, String tradeDt)
      throws RemoteException {

    int retVal = 0;

    if (isTraded) {
      QueueSession session = null;
      QueueConnection qconn = null;

      try {
        // Send a message to the exchange with trade details
        if(exchageQConnfactory == null || exchangeQueue == null) {
            InitialContext ic = new InitialContext();
            exchageQConnfactory = (QueueConnectionFactory)ic.lookup(
                                      (String)connHash.get("EXCHQCONNFACTORY"));
            exchangeQueue = (Queue)ic.lookup((String)connHash.get("EXCHQUEUE"));
        }
        // Create Queue Connection
        qconn = exchageQConnfactory.createQueueConnection();
        
        qconn.start();
        
        session = qconn.createQueueSession(false, 1);
        
        // Create a message and set the trade details 
        Message message = session.createMessage();
        message.setStringProperty("toAddress", userEmail);
        message.setStringProperty("symbol", symbol);
        message.setStringProperty("tradeDate", tradeDt);
        message.setStringProperty("tradeType", tradeType);
        message.setStringProperty("qty", quantity.toString());
        message.setJMSExpiration(18000L);
        
        // Send the message
        session.createSender(exchangeQueue).send(message);

      } catch (Exception ex)    {
        throw new RemoteException(" sendOrderDetails failed :"+
                                                ex.toString());
      } finally  {

        try   {
            if(session != null)
                session.close();
            if(qconn != null)
                qconn.close();
        } catch(JMSException je)    {
            retVal = -1;
            throw new RemoteException("JMSException : while closing session" + je.toString());
        }
      }
    } else {
      retVal = -2;
    }

    return retVal;
  }

  /**
   * Other Standard Call Back Methods
   * @since 1.0
   */
  public void ejbRemove() {}

  public void ejbActivate() {
    initialize(); // To avoid de-serialization errors
  }

  public void ejbPassivate() {}

  public void setSessionContext(SessionContext cntx) {
    sc = cntx;
  }
}