/**
*
* Copyright 2005 The Apache Software Foundation or its licensors, as applicable
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.geronimo.samples.daytrader.ejb;
import javax.ejb.*;
import javax.jms.*;
import javax.naming.*;
import org.apache.geronimo.samples.daytrader.util.*;
import java.math.BigDecimal;
import java.util.Collection;
import java.util.ArrayList;
import java.util.Iterator;
import org.apache.geronimo.samples.daytrader.*;
public class TradeBean implements SessionBean {
private SessionContext context = null;
private LocalAccountHome accountHome = null;
private LocalAccountProfileHome profileHome = null;
private LocalHoldingHome holdingHome = null;
private LocalQuoteHome quoteHome = null;
private LocalOrderHome orderHome = null;
private LocalKeySequenceHome keySequenceHome = null;
private LocalKeySequence keySequence;
private ConnectionFactory qConnFactory = null;
private Queue queue = null;
private ConnectionFactory tConnFactory = null;
private Topic streamerTopic = null;
//Boolean to signify if the Order By clause is supported by the app server.
// This can be set to false by an env. variable
private boolean orderBySQLSupported = true;
private boolean publishQuotePriceChange = true;
private boolean updateQuotePrices = true;
private void queueOrderInternal(Integer orderID, boolean twoPhase)
throws javax.jms.JMSException
{
if (Log.doTrace() ) Log.trace("TradeBean:queueOrderInternal", orderID);
Connection conn = null;
Session sess = null;
try
{
conn = qConnFactory.createConnection();
sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer msgProducer = sess.createProducer(queue);
TextMessage message = sess.createTextMessage();
message.setStringProperty("command", "neworder");
message.setIntProperty("orderID", orderID.intValue());
message.setBooleanProperty("twoPhase", twoPhase);
message.setText("neworder: orderID="+orderID + " runtimeMode=EJB twoPhase="+twoPhase);
message.setLongProperty("publishTime", System.currentTimeMillis());
if (Log.doTrace()) Log.trace("TradeBean:queueOrder Sending message: " + message.getText());
msgProducer.send(message);
}
catch (javax.jms.JMSException e)
{
throw e; // pass the exception back
}
finally {
if (conn != null )
conn.close();
if (sess != null)
sess.close();
}
}
/**
* @see TradeServices#queueOrder(Integer)
*/
public void queueOrder(Integer orderID, boolean twoPhase)
throws Exception
{
if (Log.doTrace() ) Log.trace("TradeBean:queueOrder", orderID, new Boolean(twoPhase));
if (twoPhase)
queueOrderInternal(orderID, true);
else
{
// invoke the queueOrderOnePhase method -- which requires a new transaction
// the queueOrder will run in it's own transaction thus not requiring a
// 2-phase commit
((Trade)context.getEJBObject()).queueOrderOnePhase(orderID);
}
}
/**
* @see TradeServices#queueOrderOnePhase(Integer)
* Queue the Order identified by orderID to be processed in a One Phase commit
*
* In short, this method is deployed as TXN REQUIRES NEW to avoid a
* 2-phase commit transaction across Entity and MDB access
*
*/
public void queueOrderOnePhase(Integer orderID)
throws javax.jms.JMSException, Exception
{
if (Log.doTrace() ) Log.trace("TradeBean:queueOrderOnePhase", orderID);
queueOrderInternal(orderID, false);
}
class quotePriceComparator implements java.util.Comparator {
public int compare(Object quote1, Object quote2)
{
double change1 = ((LocalQuote) quote1).getChange();
double change2 = ((LocalQuote) quote2).getChange();
return new Double(change2).compareTo(new Double(change1));
}
}
public MarketSummaryDataBean getMarketSummary()
throws Exception {
MarketSummaryDataBean marketSummaryData = null;
try
{
if (Log.doTrace() ) {
Log.trace("TradeBean:getMarketSummary -- getting market summary");
}
//Find Trade Stock Index Quotes (Top 100 quotes)
//ordered by their change in value
Collection quotes=null;
if ( orderBySQLSupported )
quotes = quoteHome.findTSIAQuotesOrderByChange();
else
quotes = quoteHome.findTSIAQuotes();
//SORT by price change the collection of stocks if the AppServer
// does not support the "ORDER BY" SQL clause
if (! orderBySQLSupported) {
//if (Log.doTrace())
Log.trace("TradeBean:getMarketSummary() -- Sorting TSIA quotes");
ArrayList sortedQuotes = new ArrayList(quotes);
java.util.Collections.sort(sortedQuotes, new quotePriceComparator());
quotes = sortedQuotes;
}
//SORT END
Object[] quoteArray = quotes.toArray();
ArrayList topGainers = new ArrayList(5);
ArrayList topLosers = new ArrayList(5);
for (int i=0; i<5; i++) topGainers.add(quoteArray[i]);
for (int i=quoteArray.length-1; i>=quoteArray.length-5; i--) topLosers.add(quoteArray[i]);
BigDecimal TSIA = FinancialUtils.ZERO;
BigDecimal openTSIA = FinancialUtils.ZERO;
double totalVolume = 0.0;
for (int i=0; i<quoteArray.length; i++)
{
LocalQuote quote = (LocalQuote)quoteArray[i];
BigDecimal price = quote.getPrice();
BigDecimal open = quote.getOpen();
double volume = quote.getVolume();
TSIA = TSIA.add(price);
openTSIA = openTSIA.add(open);
totalVolume += volume;
}
TSIA = TSIA.divide(new BigDecimal(quoteArray.length), FinancialUtils.ROUND);
openTSIA = openTSIA.divide(new BigDecimal(quoteArray.length), FinancialUtils.ROUND);
/* This is an alternate approach using ejbSelect methods
* In this approach an ejbSelect is used to select only the
* current price and open price values for the TSIA
LocalQuote quote = quoteHome.findOne();
BigDecimal TSIA = quote.getTSIA();
openTSIA = quote.getOpenTSIA();
Collection topGainers = quote.getTopGainers(5);
Collection topLosers = quote.getTopLosers(5);
LocalQuote quote = (LocalQuote)topGainers.iterator().next();
double volume = quote.getTotalVolume();
*
*/
// Convert the collections of topGainer/topLoser entities
// to collections of QuoteDataBeans
Collection topGainersData = getDataBeansCollection(topGainers);
Collection topLosersData = getDataBeansCollection(topLosers);
marketSummaryData = new MarketSummaryDataBean(TSIA, openTSIA, totalVolume, topGainersData, topLosersData);
}
catch (Exception e)
{
Log.error("TradeBean:getMarketSummary", e);
throw new EJBException("TradeBean:getMarketSummary -- error ", e);
}
return marketSummaryData;
}
public QuoteDataBean createQuote(String symbol, String companyName, BigDecimal price)
throws CreateException, Exception
{
try
{
LocalQuote quote = quoteHome.create(symbol, companyName, price);
QuoteDataBean quoteData = quote.getDataBean();
if (Log.doTrace()) Log.trace("TradeBean:createQuote-->" + quoteData);
return quoteData;
}
catch (Exception e)
{
Log.error("TradeBean:createQuote -- exception creating Quote", e);
throw new EJBException(e);
}
}
public QuoteDataBean getQuote(String symbol)
throws Exception
{
if (Log.doTrace()) Log.trace("TradeBean:getQuote", symbol);
QuoteDataBean quoteData;
try {
LocalQuote quote = quoteHome.findByPrimaryKey(symbol);
quoteData = quote.getDataBean();
} catch (FinderException fe) {
//Cannot find quote for given symbol
Log.error("TradeBean:getQuote--> Symbol: " + symbol + " cannot be found");
BigDecimal z = new BigDecimal(0.0);
quoteData = new QuoteDataBean("Error: Symbol " + symbol + " not found", "", 0.0, z, z, z, z, 0.0 );
}
return quoteData;
}
public Collection getAllQuotes()
throws Exception
{
if (Log.doTrace()) Log.trace("TradeBean:getAllQuotes");
Collection quoteBeans = new ArrayList();
try {
Collection quotes = quoteHome.findAll();
for (Iterator it = quotes.iterator(); it.hasNext(); ) {
LocalQuote quote = (LocalQuote)it.next();
quoteBeans.add(quote.getDataBean());
}
}
catch (FinderException fe) {
Log.error("TradeBean:getAllQuotes");
}
return quoteBeans;
}
public QuoteDataBean updateQuotePriceVolume(String symbol, BigDecimal changeFactor, double sharesTraded)
throws Exception
{
if ( TradeConfig.getUpdateQuotePrices() == false )
return new QuoteDataBean();
if (Log.doTrace())
Log.trace("TradeBean:updateQuote", symbol, changeFactor);
QuoteDataBean quoteData;
try {
LocalQuote quote = quoteHome.findByPrimaryKeyForUpdate(symbol);
BigDecimal oldPrice = quote.getPrice();
if (quote.getPrice().equals(TradeConfig.PENNY_STOCK_PRICE)) {
changeFactor = TradeConfig.PENNY_STOCK_RECOVERY_MIRACLE_MULTIPLIER;
}
BigDecimal newPrice = changeFactor.multiply(oldPrice).setScale(2, BigDecimal.ROUND_HALF_UP);
quote.updatePrice(newPrice);
quote.addToVolume(sharesTraded);
quoteData = quote.getDataBean();
((Trade)context.getEJBObject()).publishQuotePriceChange(quoteData, oldPrice, changeFactor, sharesTraded);
} catch (FinderException fe) {
//Cannot find quote for given symbol
Log.error("TradeBean:updateQuotePriceVolume--> Symbol: " + symbol + " cannot be found");
quoteData = new QuoteDataBean("Error: Symbol " + symbol + " not found");
}
return quoteData;
}
public void publishQuotePriceChange(QuoteDataBean quoteData, BigDecimal oldPrice, BigDecimal changeFactor, double sharesTraded)
throws Exception
{
if ( publishQuotePriceChange == false)
return;
if (Log.doTrace())
Log.trace("TradeBean:publishQuotePricePublishing -- quoteData = " + quoteData);
Connection conn = null;
Session sess = null;
try
{
conn = tConnFactory.createConnection();
sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer msgProducer = sess.createProducer(streamerTopic);
TextMessage message = sess.createTextMessage();
String command = "updateQuote";
message.setStringProperty("command", command);
message.setStringProperty("symbol", quoteData.getSymbol() );
message.setStringProperty("company", quoteData.getCompanyName() );
message.setStringProperty("price", quoteData.getPrice().toString());
message.setStringProperty("oldPrice",oldPrice.toString());
message.setStringProperty("open", quoteData.getOpen().toString());
message.setStringProperty("low", quoteData.getLow().toString());
message.setStringProperty("high", quoteData.getHigh().toString());
message.setDoubleProperty("volume", quoteData.getVolume());
message.setStringProperty("changeFactor", changeFactor.toString());
message.setDoubleProperty("sharesTraded", sharesTraded);
message.setLongProperty("publishTime", System.currentTimeMillis());
message.setText("Update Stock price for " + quoteData.getSymbol() + " old price = " + oldPrice + " new price = " + quoteData.getPrice());
msgProducer.send(message);
}
catch (Exception e)
{
throw e; // pass the exception back
}
finally {
if (conn != null)
conn.close();
if (sess != null)
sess.close();
}
}
public OrderDataBean buy(String userID, String symbol, double quantity, int orderProcessingMode)
throws Exception
{
LocalOrder order=null;
BigDecimal total;
try {
if (Log.doTrace())
Log.trace("TradeBean:buy", userID, symbol, new Double(quantity), new Integer(orderProcessingMode));
/* The following commented code shows alternative forms of the finder needed for this
* method
* The first alternative requires a 2-table join. Some database cannot allocate an Update
* Lock on a join select.
*
* The second alternative shows the finder being executed without allocation an update
* lock on the row. Normally, an update lock would not be necessary, but is required if
* the same user logs in multiple times to avoid a deadlock situation.
*
* The third alternative runs the finder and allocates an update lock on the row(s)
*
LocalAccount account = accountHome.findByUserIDForUpdate(userID);
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKey(userID)).getAccountForUpdate();
*/
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate();
LocalQuote quote = quoteHome.findByPrimaryKey(symbol);
LocalHolding holding = null; //The holding will be created by this buy order
Integer orderID = keySequence.getNextID("order");
order = createOrder(orderID, account, quote, holding, "buy", quantity);
//UPDATE - account should be credited during completeOrder
BigDecimal price = quote.getPrice();
BigDecimal orderFee = order.getOrderFee();
BigDecimal balance = account.getBalance();
total = (new BigDecimal(quantity).multiply(price)).add(orderFee);
account.setBalance(balance.subtract(total));
if (orderProcessingMode == TradeConfig.SYNCH)
completeOrderInternal(order.getOrderID());
else if (orderProcessingMode == TradeConfig.ASYNCH)
// Invoke the queueOrderOnePhase method w/ TXN requires new attribute
// to side-step a 2-phase commit across DB and JMS access
queueOrder(order.getOrderID(), false);
else //TradeConfig.ASYNC_2PHASE
queueOrder(order.getOrderID(), true);
}
catch (Exception e)
{
Log.error("TradeBean:buy("+userID+","+symbol+","+quantity+") --> failed", e);
/* On exception - cancel the order */
if (order != null) order.cancel();
throw new EJBException(e);
}
return order.getDataBean();
}
public OrderDataBean sell(String userID, Integer holdingID, int orderProcessingMode)
throws Exception
{
LocalOrder order=null;
BigDecimal total;
try {
if (Log.doTrace())
Log.trace("TradeBean:sell", userID, holdingID, new Integer(orderProcessingMode));
/* Some databases cannot allocate an update lock on a JOIN
* use the second approach below to acquire update lock
LocalAccount account = accountHome.findByUserIDForUpdate(userID);
*/
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate();
LocalHolding holding;
try {
holding = holdingHome.findByPrimaryKeyForUpdate(holdingID);
}
catch (ObjectNotFoundException oe)
{
Log.error("TradeBean:sell User " + userID + " attempted to sell holding " + holdingID + " which has already been sold");
OrderDataBean orderData = new OrderDataBean();
orderData.setOrderStatus("cancelled");
return orderData;
}
LocalQuote quote = holding.getQuote();
double quantity = holding.getQuantity();
Integer orderID = keySequence.getNextID("order");
order = createOrder(orderID, account, quote, holding, "sell", quantity);
//UPDATE the holding purchase data to signify this holding is "inflight" to be sold
// -- could add a new holdingStatus attribute to holdingEJB
holding.setPurchaseDate(new java.sql.Timestamp(0));
//UPDATE - account should be credited during completeOrder
BigDecimal price = quote.getPrice();
BigDecimal orderFee = order.getOrderFee();
BigDecimal balance = account.getBalance();
total = (new BigDecimal(quantity).multiply(price)).subtract(orderFee);
account.setBalance(balance.add(total));
if (orderProcessingMode == TradeConfig.SYNCH)
completeOrderInternal(order.getOrderID());
else if (orderProcessingMode == TradeConfig.ASYNCH)
queueOrder(order.getOrderID(), false);
else //TradeConfig.ASYNC_2PHASE
queueOrder(order.getOrderID(), true);
}
catch (Exception e)
{
Log.error("TradeBean:sell("+userID+","+holdingID+") --> failed", e);
if (order != null) order.cancel();
//UPDATE - handle all exceptions like:
throw new EJBException("TradeBean:sell("+userID+","+holdingID+")",e);
}
return order.getDataBean();
}
public Collection getOrders(String userID)
throws FinderException, Exception
{
if (Log.doTrace())
Log.trace("TradeBean:getOrders", userID);
/* The following commented code shows alternative forms of the finder needed for this
* method
* The first alternative requires a 2-table join. Some database cannot allocate an Update
* Lock on a join select.
*
* The second alternative shows the finder being executed without allocation an update
* lock on the row. Normally, an update lock would not be necessary, but is required if
* the same user logs in multiple times to avoid a deadlock situation.
*
* The third alternative runs the finder and allocates an update lock on the row(s)
*
Collection orders = accountHome.findByUserID(userID).getOrders();
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKey(userID)).getAccountForUpdate();
*/
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate();
Collection orders = account.getOrders();
ArrayList dataBeans = new ArrayList();
if ( orders == null ) return dataBeans;
Iterator it = orders.iterator();
//TODO: return top 5 orders for now -- next version will add a getAllOrders method
// also need to get orders sorted by order id descending
int i=0;
while ( (it.hasNext()) && (i++ < 5))
dataBeans.add(((LocalOrder) it.next()).getDataBean());
return dataBeans;
}
public Collection getClosedOrders(String userID)
throws FinderException, Exception
{
if (Log.doTrace())
Log.trace("TradeBean:getClosedOrders", userID);
ArrayList dataBeans = new ArrayList();
try {
/* The following commented code shows alternative forms of the finder needed for this
* method
* The first alternative requires a 2-table join. Some database cannot allocate an Update
* Lock on a join select.
*
* The second alternative shows the finder being executed without allocation an update
* lock on the row. Normally, an update lock would not be necessary, but is required if
* the same user logs in multiple times to avoid a deadlock situation.
*
* The third alternative runs the finder and allocates an update lock on the row(s)
*
Collection orders = orderHome.findClosedOrdersForUpdate(userID);
*
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKey(userID)).getAccount();
*/
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate();
//Get the primary keys for all the closed Orders for this account.
Collection ordersKeys = account.getClosedOrders();
if ( ordersKeys == null ) return dataBeans;
Iterator it = ordersKeys.iterator();
while (it.hasNext())
{
Integer orderKey = (Integer) it.next();
LocalOrder order = (LocalOrder) orderHome.findByPrimaryKeyForUpdate(orderKey);
//Complete the order
order.setOrderStatus("completed");
dataBeans.add(order.getDataBean());
}
}
catch (Exception e)
{
Log.error("TradeBean.getClosedOrders", e);
throw new EJBException("TradeBean.getClosedOrders - error", e);
}
return dataBeans;
}
public OrderDataBean completeOrder(Integer orderID, boolean twoPhase)
throws Exception {
if (Log.doTrace()) Log.trace("TradeBean:completeOrder", orderID + " twoPhase="+twoPhase);
if (twoPhase)
return completeOrderInternal(orderID);
else
{
// invoke the completeOrderOnePhase -- which requires a new transaction
// the completeOrder will run in it's own transaction thus not requiring a
// 2-phase commit
return ((Trade)context.getEJBObject()).completeOrderOnePhase(orderID);
}
}
//completeOrderOnePhase method is deployed w/ TXN_REQUIRES_NEW
//thus the completeOrder call from the MDB should not require a 2-phase commit
public OrderDataBean completeOrderOnePhase(Integer orderID)
throws Exception {
if (Log.doTrace()) Log.trace("TradeBean:completeOrderOnePhase", orderID);
return completeOrderInternal(orderID);
}
private OrderDataBean completeOrderInternal(Integer orderID)
throws Exception
{
LocalOrder order = orderHome.findByPrimaryKeyForUpdate(orderID);
if (order == null)
{
Log.error("TradeBean:completeOrderInternal -- Unable to find Order " + orderID + " FBPK returned " + order);
order.cancel();
return order.getDataBean();
}
String orderType = order.getOrderType();
String orderStatus = order.getOrderStatus();
if (order.isCompleted())
throw new EJBException("Error: attempt to complete Order that is already completed\n" + order);
LocalAccount account = order.getAccount();
LocalQuote quote = order.getQuote();
LocalHolding holding = order.getHoldingForUpdate();
BigDecimal price = order.getPrice();
double quantity = order.getQuantity();
BigDecimal orderFee = order.getOrderFee();
BigDecimal balance = account.getBalance();
/*
* getProfile is marked as Pess. Update to get a DB lock
* Here we invoke getProfileForRead which is deployed to not
* lock the DB (Pess. Read)
*/
String userID = account.getProfile().getUserID();
/*
* total = (quantity * purchasePrice) + orderFee
*/
if (Log.doTrace()) Log.trace(
"TradeBeanInternal:completeOrder--> Completing Order " + order.getOrderID()
+ "\n\t Order info: " + order
+ "\n\t Account info: " + account
+ "\n\t Quote info: " + quote
+ "\n\t Holding info: " + holding);
if (order.isBuy()) {
/* Complete a Buy operation
* - create a new Holding for the Account
* - deduct the Order cost from the Account balance
*/
LocalHolding newHolding = createHolding(account, quote, quantity, price);
order.setHolding(newHolding);
}
if (order.isSell()) {
/* Complete a Sell operation
* - remove the Holding from the Account
* - deposit the Order proceeds to the Account balance
*/
if ( holding == null )
{
Log.error("TradeBean:completeOrderInternal -- Unable to sell order " + order.getOrderID() + " holding already sold");
order.cancel();
return order.getDataBean();
}
else
{
holding.remove();
holding = null;
}
// This is managed by the container
// order.setHolding(null);
}
order.setOrderStatus("closed");
order.setCompletionDate(new java.sql.Timestamp(System.currentTimeMillis()));
if (Log.doTrace()) Log.trace(
"TradeBean:completeOrder--> Completed Order " + order.getOrderID()
+ "\n\t Order info: " + order
+ "\n\t Account info: " + account
+ "\n\t Quote info: " + quote
+ "\n\t Holding info: " + holding);
BigDecimal priceChangeFactor = TradeConfig.getRandomPriceChangeFactor();
if(Log.doTrace())
Log.trace("Calling TradeAction:orderCompleted from Session EJB using Session Object");
//FUTURE All getEJBObjects could be local -- need to add local I/F
TradeServices trade = (TradeServices)context.getEJBObject();
TradeAction tradeAction = new TradeAction(trade);
//signify this order for user userID is complete
tradeAction.orderCompleted(userID, orderID);
return order.getDataBean();
}
//These methods are used to provide the 1-phase commit runtime option for TradeDirect
// Basically these methods are deployed as txn requires new and invoke TradeDirect methods
// There is no mechanism outside of EJB to start a new transaction
public OrderDataBean completeOrderOnePhaseDirect(Integer orderID)
throws Exception {
if (Log.doTrace())
Log.trace("TradeBean:completeOrderOnePhaseDirect -- completing order by calling TradeDirect orderID=" +orderID);
return (new org.apache.geronimo.samples.daytrader.direct.TradeDirect()).completeOrderOnePhase(orderID);
}
public void cancelOrderOnePhaseDirect(Integer orderID)
throws Exception {
if (Log.doTrace())
Log.trace("TradeBean:cancelOrderOnePhaseDirect -- cancelling order by calling TradeDirect orderID=" +orderID);
(new org.apache.geronimo.samples.daytrader.direct.TradeDirect()).cancelOrderOnePhase(orderID);
}
public void cancelOrder(Integer orderID, boolean twoPhase)
throws Exception {
if (Log.doTrace()) Log.trace("TradeBean:cancelOrder", orderID + " twoPhase="+twoPhase);
if (twoPhase)
cancelOrderInternal(orderID);
else
{
// invoke the cancelOrderOnePhase -- which requires a new transaction
// the completeOrder will run in it's own transaction thus not requiring a
// 2-phase commit
((Trade)context.getEJBObject()).cancelOrderOnePhase(orderID);
}
}
//cancelOrderOnePhase method is deployed w/ TXN_REQUIRES_NEW
//thus the completeOrder call from the MDB should not require a 2-phase commit
public void cancelOrderOnePhase(Integer orderID)
throws Exception {
if (Log.doTrace()) Log.trace("TradeBean:cancelOrderOnePhase", orderID);
cancelOrderInternal(orderID);
}
private void cancelOrderInternal(Integer orderID)
throws Exception
{
LocalOrder order = orderHome.findByPrimaryKeyForUpdate(orderID);
order.cancel();
}
public void orderCompleted(String userID, Integer orderID)
throws Exception
{
throw new UnsupportedOperationException("TradeBean:orderCompleted method not supported");
}
public LocalHolding createHolding(
LocalAccount account,
LocalQuote quote,
double quantity,
BigDecimal purchasePrice)
throws Exception
{
LocalHolding newHolding = null;
Integer holdingID = null;
try {
if (Log.doTrace())
Log.trace("TradeBean:createHolding");
holdingID = keySequence.getNextID("holding");
newHolding =
holdingHome.create(holdingID, account, quote, quantity, purchasePrice);
} catch (Exception e) {
String error = "Failed to create Holding for account: " + account.getAccountID()
+ " with quote: " + quote.getSymbol()
+ " holdingID: " + holdingID
+ " quantity: " + quantity + "\n";
Log.error(e, error );
throw new EJBException(error,e);
}
return newHolding;
}
public Collection getHoldings(String userID)
throws FinderException, Exception
{
if (Log.doTrace())
Log.trace("TradeBean:getHoldings", userID);
Collection holdings = holdingHome.findByUserID(userID);
if (Log.doTrace())
Log.trace("Got holdings collection size="+holdings.size());
ArrayList dataBeans = new ArrayList();
if ( holdings == null ) return dataBeans;
Iterator it = holdings.iterator();
while (it.hasNext()) {
HoldingDataBean holdingData = ((LocalHolding) it.next()).getDataBean();
dataBeans.add(holdingData);
}
return dataBeans;
}
public HoldingDataBean getHolding(Integer holdingID)
throws FinderException, Exception
{
if (Log.doTrace())
Log.trace("TradeBean:getHolding", holdingID);
LocalHolding holding = holdingHome.findByPrimaryKey(holdingID);
HoldingDataBean holdingData = holding.getDataBean();
if (Log.doTrace())
Log.trace("TradeBean:getHolding " + holdingData);
return holdingData;
}
public LocalOrder createOrder(
int orderID,
LocalAccount account,
LocalQuote quote,
LocalHolding holding,
String orderType,
double quantity)
throws Exception {
try
{
return createOrder(new Integer(orderID), account, quote, holding, orderType, quantity);
}
catch(Exception e)
{
Log.error("TradeBean:createOrder -- failed to create Order", e);
throw new EJBException("TradeBean:createOrder -- failed to create Order", e);
}
}
public LocalOrder createOrder(
Integer orderID,
LocalAccount account,
LocalQuote quote,
LocalHolding holding,
String orderType,
double quantity)
throws CreateException, Exception {
LocalOrder order = null;
if (Log.doTrace() )
Log.trace(
"TradeBean:createOrder(orderID="
+ orderID
+ " account="
+ ((account == null) ? null : account.getPrimaryKey())
+ " quote="
+ ((quote == null) ? null : quote.getPrimaryKey())
+ " orderType="
+ orderType
+ " quantity="
+ quantity);
try
{
order = orderHome.create(orderID, account, quote, holding, orderType, quantity);
}
catch(Exception e)
{
Log.error("TradeBean:createOrder -- failed to create Order", e);
throw new EJBException("TradeBean:createOrder -- failed to create Order", e);
}
return order;
}
public AccountDataBean login(String userID, String password)
throws FinderException, Exception {
/* The following commented code shows alternative forms of the finder needed for this
* method
* The first alternative requires a 2-table join. Some database cannot allocate an Update
* Lock on a join select.
*
* The second alternative shows the finder being executed without allocation an update
* lock on the row. Normally, an update lock would not be necessary, but is required if
* the same user logs in multiple times to avoid a deadlock situation.
*
* The third alternative runs the finder and allocates an update lock on the row(s)
*
LocalAccount account = accountHome.findByUserIDForUpdate(userID);
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKey(userID)).getAccountForUpdate();
*/
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate();
if (Log.doTrace())
Log.trace("TradeBean:login", userID, password);
account.login(password);
AccountDataBean accountData = account.getDataBean();
if (Log.doTrace())
Log.trace("TradeBean:login(" + userID + "," + password + ") success" + accountData);
return accountData;
}
public void logout(String userID)
throws FinderException, Exception {
if (Log.doTrace())
Log.trace("TradeBean:logout", userID);
/* The following commented code shows alternative forms of the finder needed for this
* method
* The first alternative requires a 2-table join. Some database cannot allocate an Update
* Lock on a join select.
*
* The second alternative shows the finder being executed without allocation an update
* lock on the row. Normally, an update lock would not be necessary, but is required if
* the same user logs in multiple times to avoid a deadlock situation.
*
* The third alternative runs the finder and allocates an update lock on the row(s)
* LocalAccount account = accountHome.findByUserIDForUpdate(userID);
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKey(userID)).getAccountForUpdate();
*
*/
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate();
if (Log.doTrace()) Log.trace("TradeBean:logout(" + userID + ") success");
account.logout();
}
public AccountDataBean register(
String userID,
String password,
String fullname,
String address,
String email,
String creditcard,
BigDecimal openBalance)
throws CreateException, Exception {
LocalAccount account = null;
try {
if (Log.doTrace())
Log.trace("TradeBean:register", userID, password, fullname, address, email, creditcard, openBalance);
Integer accountID = keySequence.getNextID("account");
account =
accountHome.create(
accountID,
userID,
password,
openBalance,
fullname,
address,
email,
creditcard);
}
catch (Exception e)
{
Log.error("Failed to register new Account\n" + e);
throw new EJBException("Failed to register new Account\n", e);
}
AccountDataBean accountData = account.getDataBean();
return accountData;
}
public AccountDataBean getAccountData(String userID)
throws FinderException, Exception {
if (Log.doTrace())
Log.trace("TradeBean:getAccountData", userID);
/* The following commented code shows alternative forms of the finder needed for this
* method
* The first alternative requires a 2-table join. Some database cannot allocate an Update
* Lock on a join select.
*
* The second alternative shows the finder being executed without allocation an update
* lock on the row. Normally, an update lock would not be necessary, but is required if
* the same user logs in multiple times to avoid a deadlock situation.
*
* The third alternative runs the finder and allocates an update lock on the row(s)
*
LocalAccount account = accountHome.findByUserID(userID);
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKey(userID)).getAccountForUpdate();
*/
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate();
AccountDataBean accountData = account.getDataBean();
return accountData;
}
public AccountProfileDataBean getAccountProfileData(String userID)
throws FinderException, Exception {
if (Log.doTrace())
Log.trace("TradeBean:getAccountProfileData", userID);
/* The following commented code shows alternative forms of the finder needed for this
* method
* The first alternative requires a 2-table join. Some database cannot allocate an Update
* Lock on a join select.
*
* The second alternative shows the finder being executed without allocation an update
* lock on the row. Normally, an update lock would not be necessary, but is required if
* the same user logs in multiple times to avoid a deadlock situation.
*
* The third alternative runs the finder and allocates an update lock on the row(s)
*
LocalAccount account = accountHome.findByUserID(userID);
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKey(userID)).getAccountForUpdate();
*/
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate();
AccountProfileDataBean accountProfileData = account.getProfileDataBean();
return accountProfileData;
}
public AccountProfileDataBean updateAccountProfile(AccountProfileDataBean accountProfileData)
throws FinderException, Exception {
if (Log.doTrace())
Log.trace("TradeBean:updateAccountProfileData", accountProfileData);
LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(accountProfileData.getUserID())).getAccountForUpdate();
accountProfileData =
account.updateAccountProfile(accountProfileData);
return accountProfileData;
}
public RunStatsDataBean resetTrade(boolean deleteAll)
throws Exception
{
if (Log.doTrace())
Log.trace("TradeBean:resetTrade", new Boolean(deleteAll));
//Clear MDB Statistics
MDBStats.getInstance().reset();
// Reset Trade
return new org.apache.geronimo.samples.daytrader.direct.TradeDirect().resetTrade(deleteAll);
}
private Collection getDataBeansCollection(Collection entities)
{
ArrayList dataBeans = new ArrayList();
if ( (entities == null) || (entities.size()<= 0) )
return dataBeans;
Iterator it = entities.iterator();
while (it.hasNext()) {
LocalQuote entity = (LocalQuote) it.next();
Object o = (Object)entity.getDataBean();
dataBeans.add(o);
}
return dataBeans;
}
/**
* provides a simple session method with no database access to test performance of a simple
* path through a stateless session
* @param investment amount
* @param NetValue current value
* @return return on investment as a percentage
*/
public double investmentReturn(double investment, double NetValue)
throws Exception
{
if (Log.doTrace())
Log.trace("TradeBean:investmentReturn");
double diff = NetValue - investment;
double ir = diff / investment;
return ir;
}
/**
* This method provides a ping test for a 2-phase commit operation
*
* @param symbol to lookup
* @return quoteData after sending JMS message
*/
public QuoteDataBean pingTwoPhase(String symbol)
throws Exception
{
if (Log.doTrace()) Log.trace("TradeBean:pingTwoPhase", symbol);
QuoteDataBean quoteData=null;
Connection conn = null;
Session sess = null;
try {
//Get a Quote and send a JMS message in a 2-phase commit
LocalQuote quote = quoteHome.findByPrimaryKey(symbol);
quoteData = quote.getDataBean();
conn = qConnFactory.createConnection();
sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
MessageProducer msgProducer = sess.createProducer(queue);
TextMessage message = sess.createTextMessage();
String command= "ping";
message.setStringProperty("command", command);
message.setLongProperty("publishTime", System.currentTimeMillis());
message.setText("Ping message for queue java:comp/env/jms/TradeBrokerQueue sent from TradeSessionEJB:pingTwoPhase at " + new java.util.Date());
msgProducer.send(message);
}
catch (Exception e) {
Log.error("TradeBean:pingTwoPhase -- exception caught",e);
}
finally {
if (conn != null)
conn.close();
if (sess != null)
sess.close();
}
return quoteData;
}
/* Required javax.ejb.SessionBean interface methods */
public TradeBean() {
}
private static boolean warnJMSFailure = true;
public void ejbCreate() throws CreateException {
try {
if (Log.doTrace())
Log.trace("TradeBean:ejbCreate -- JNDI lookups of EJB and JMS resources");
InitialContext ic = new InitialContext();
quoteHome = (LocalQuoteHome) ic.lookup("java:comp/env/ejb/Quote");
accountHome = (LocalAccountHome) ic.lookup("java:comp/env/ejb/Account");
profileHome = (LocalAccountProfileHome) ic.lookup("java:comp/env/ejb/AccountProfile");
holdingHome = (LocalHoldingHome) ic.lookup("java:comp/env/ejb/Holding");
orderHome = (LocalOrderHome) ic.lookup("java:comp/env/ejb/Order");
keySequenceHome = (LocalKeySequenceHome) ic.lookup("java:comp/env/ejb/KeySequence");
orderBySQLSupported = ( (Boolean) ic.lookup("java:comp/env/orderBySQLSupported") ).booleanValue();
publishQuotePriceChange = ( (Boolean) ic.lookup("java:comp/env/publishQuotePriceChange") ).booleanValue();
updateQuotePrices = ( (Boolean) ic.lookup("java:comp/env/updateQuotePrices") ).booleanValue();
TradeConfig.setUpdateQuotePrices(updateQuotePrices);
try
{
qConnFactory = (ConnectionFactory) ic.lookup("java:comp/env/jms/QueueConnectionFactory");
queue = (Queue) ic.lookup("java:comp/env/jms/TradeBrokerQueue");
tConnFactory = (ConnectionFactory) ic.lookup("java:comp/env/jms/TopicConnectionFactory");
streamerTopic = (Topic) ic.lookup("java:comp/env/jms/TradeStreamerTopic");
}
catch (Exception e)
{
if (warnJMSFailure == true)
{
warnJMSFailure = false;
Log.error("TradeBean:ejbCreate Unable to lookup JMS Resources\n\t -- Asynchronous mode will not work correctly and Quote Price change publishing will be disabled", e);
}
publishQuotePriceChange = false;
}
} catch (Exception e) {
Log.error("TradeBean:ejbCreate: Lookup of Local Entity Homes Failed\n" + e);
e.printStackTrace();
//UPDATE
//throw new CreateException(e.toString());
}
if ((quoteHome == null) ||
( accountHome == null) ||
( holdingHome == null) ||
( orderHome == null) ||
( keySequenceHome == null) //||
// ( qConnFactory == null) ||
// ( queue == null) ||
// ( tConnFactory == null) ||
// ( streamerTopic == null) ||
)
{
String error = "TradeBean:ejbCreate() JNDI lookup of Trade resource failed\n" +
"\n\t quoteHome="+quoteHome+
"\n\t accountHome="+ accountHome+
"\n\t holdingHome="+ holdingHome+
"\n\t orderHome="+ orderHome+
"\n\t qConnFactory="+ qConnFactory+
"\n\t queue="+ queue+
"\n\t tConnFactory="+ tConnFactory+
"\n\t streamerTopic="+ streamerTopic;
Log.error(error);
//UPDATE
//throw new EJBException(error);
}
keySequence = keySequenceHome.create();
}
public void ejbRemove()
{
try
{
if (Log.doTrace())
Log.trace("TradeBean:ejbRemove");
}
catch (Exception e)
{
Log.error(e,"Unable to close Queue or Topic connection on Session EJB remove");
}
}
public void ejbActivate() {
}
public void ejbPassivate() {
}
public void setSessionContext(SessionContext sc) {
context = sc;
}
}