001 /** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.geronimo.samples.daytrader.ejb; 018 019 import javax.ejb.*; 020 import javax.jms.*; 021 import javax.naming.*; 022 023 import org.apache.geronimo.samples.daytrader.util.*; 024 025 import java.math.BigDecimal; 026 import java.rmi.RemoteException; 027 import java.util.Collection; 028 import java.util.ArrayList; 029 import java.util.Iterator; 030 import org.apache.geronimo.samples.daytrader.*; 031 032 public class TradeBean implements SessionBean { 033 034 private SessionContext context = null; 035 private LocalAccountHome accountHome = null; 036 private LocalAccountProfileHome profileHome = null; 037 private LocalHoldingHome holdingHome = null; 038 private LocalQuoteHome quoteHome = null; 039 private LocalOrderHome orderHome = null; 040 private LocalKeySequenceHome keySequenceHome = null; 041 private LocalKeySequence keySequence; 042 043 private ConnectionFactory qConnFactory = null; 044 private Queue queue = null; 045 private ConnectionFactory tConnFactory = null; 046 private Topic streamerTopic = null; 047 048 //Boolean to signify if the Order By clause is supported by the app server. 049 // This can be set to false by an env. variable 050 private boolean orderBySQLSupported = true; 051 private boolean publishQuotePriceChange = true; 052 private boolean updateQuotePrices = true; 053 054 private void queueOrderInternal(Integer orderID, boolean twoPhase) 055 throws javax.jms.JMSException 056 { 057 if (Log.doTrace() ) Log.trace("TradeBean:queueOrderInternal", orderID); 058 059 Connection conn = null; 060 Session sess = null; 061 try 062 { 063 conn = qConnFactory.createConnection(); 064 sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); 065 MessageProducer msgProducer = sess.createProducer(queue); 066 TextMessage message = sess.createTextMessage(); 067 068 message.setStringProperty("command", "neworder"); 069 message.setIntProperty("orderID", orderID.intValue()); 070 message.setBooleanProperty("twoPhase", twoPhase); 071 message.setText("neworder: orderID="+orderID + " runtimeMode=EJB twoPhase="+twoPhase); 072 message.setLongProperty("publishTime", System.currentTimeMillis()); 073 074 if (Log.doTrace()) Log.trace("TradeBean:queueOrder Sending message: " + message.getText()); 075 msgProducer.send(message); 076 } 077 catch (javax.jms.JMSException e) 078 { 079 throw e; // pass the exception back 080 } 081 082 finally { 083 if (conn != null ) 084 conn.close(); 085 if (sess != null) 086 sess.close(); 087 } 088 } 089 090 /** 091 * @see TradeServices#queueOrder(Integer) 092 */ 093 094 public void queueOrder(Integer orderID, boolean twoPhase) 095 throws Exception 096 { 097 if (Log.doTrace() ) Log.trace("TradeBean:queueOrder", orderID, new Boolean(twoPhase)); 098 if (twoPhase) 099 queueOrderInternal(orderID, true); 100 else 101 { 102 // invoke the queueOrderOnePhase method -- which requires a new transaction 103 // the queueOrder will run in it's own transaction thus not requiring a 104 // 2-phase commit 105 ((Trade)context.getEJBObject()).queueOrderOnePhase(orderID); 106 } 107 } 108 109 110 /** 111 * @see TradeServices#queueOrderOnePhase(Integer) 112 * Queue the Order identified by orderID to be processed in a One Phase commit 113 * 114 * In short, this method is deployed as TXN REQUIRES NEW to avoid a 115 * 2-phase commit transaction across Entity and MDB access 116 * 117 */ 118 119 public void queueOrderOnePhase(Integer orderID) 120 throws RemoteException 121 { 122 try{ 123 if (Log.doTrace() ) Log.trace("TradeBean:queueOrderOnePhase", orderID); 124 queueOrderInternal(orderID, false); 125 } catch (Exception e){ 126 throw new RemoteException(e.getMessage(), e); 127 } 128 } 129 130 class quotePriceComparator implements java.util.Comparator { 131 public int compare(Object quote1, Object quote2) 132 { 133 double change1 = ((LocalQuote) quote1).getChange(); 134 double change2 = ((LocalQuote) quote2).getChange(); 135 return new Double(change2).compareTo(new Double(change1)); 136 } 137 } 138 139 public MarketSummaryDataBean getMarketSummary() 140 throws Exception { 141 142 143 MarketSummaryDataBean marketSummaryData = null; 144 try 145 { 146 if (Log.doTrace() ) { 147 Log.trace("TradeBean:getMarketSummary -- getting market summary"); 148 } 149 150 //Find Trade Stock Index Quotes (Top 100 quotes) 151 //ordered by their change in value 152 Collection quotes=null; 153 if ( orderBySQLSupported ) 154 quotes = quoteHome.findTSIAQuotesOrderByChange(); 155 else 156 quotes = quoteHome.findTSIAQuotes(); 157 158 //SORT by price change the collection of stocks if the AppServer 159 // does not support the "ORDER BY" SQL clause 160 if (! orderBySQLSupported) { 161 //if (Log.doTrace()) 162 Log.trace("TradeBean:getMarketSummary() -- Sorting TSIA quotes"); 163 ArrayList sortedQuotes = new ArrayList(quotes); 164 java.util.Collections.sort(sortedQuotes, new quotePriceComparator()); 165 quotes = sortedQuotes; 166 } 167 //SORT END 168 Object[] quoteArray = quotes.toArray(); 169 ArrayList topGainers = new ArrayList(5); 170 ArrayList topLosers = new ArrayList(5); 171 BigDecimal TSIA = FinancialUtils.ZERO; 172 BigDecimal openTSIA = FinancialUtils.ZERO; 173 double totalVolume = 0.0; 174 175 if (quoteArray.length > 5) { 176 for (int i = 0; i < 5; i++) 177 topGainers.add(quoteArray[i]); 178 for (int i = quoteArray.length - 1; i >= quoteArray.length - 5; i--) 179 topLosers.add(quoteArray[i]); 180 181 for (int i = 0; i < quoteArray.length; i++) { 182 LocalQuote quote = (LocalQuote) quoteArray[i]; 183 BigDecimal price = quote.getPrice(); 184 BigDecimal open = quote.getOpen(); 185 double volume = quote.getVolume(); 186 TSIA = TSIA.add(price); 187 openTSIA = openTSIA.add(open); 188 totalVolume += volume; 189 } 190 TSIA = TSIA.divide(new BigDecimal(quoteArray.length), 191 FinancialUtils.ROUND); 192 openTSIA = openTSIA.divide(new BigDecimal(quoteArray.length), 193 FinancialUtils.ROUND); 194 } 195 /* This is an alternate approach using ejbSelect methods 196 * In this approach an ejbSelect is used to select only the 197 * current price and open price values for the TSIA 198 LocalQuote quote = quoteHome.findOne(); 199 BigDecimal TSIA = quote.getTSIA(); 200 openTSIA = quote.getOpenTSIA(); 201 Collection topGainers = quote.getTopGainers(5); 202 Collection topLosers = quote.getTopLosers(5); 203 LocalQuote quote = (LocalQuote)topGainers.iterator().next(); 204 double volume = quote.getTotalVolume(); 205 * 206 */ 207 208 // Convert the collections of topGainer/topLoser entities 209 // to collections of QuoteDataBeans 210 Collection topGainersData = getDataBeansCollection(topGainers); 211 Collection topLosersData = getDataBeansCollection(topLosers); 212 213 marketSummaryData = new MarketSummaryDataBean(TSIA, openTSIA, totalVolume, topGainersData, topLosersData); 214 } 215 catch (Exception e) 216 { 217 Log.error("TradeBean:getMarketSummary", e); 218 throw new EJBException("TradeBean:getMarketSummary -- error ", e); 219 } 220 return marketSummaryData; 221 } 222 223 public QuoteDataBean createQuote(String symbol, String companyName, BigDecimal price) 224 throws CreateException, Exception 225 { 226 try 227 { 228 LocalQuote quote = quoteHome.create(symbol, companyName, price); 229 QuoteDataBean quoteData = quote.getDataBean(); 230 if (Log.doTrace()) Log.trace("TradeBean:createQuote-->" + quoteData); 231 return quoteData; 232 } 233 catch (Exception e) 234 { 235 Log.error("TradeBean:createQuote -- exception creating Quote", e); 236 throw new EJBException(e); 237 } 238 } 239 240 public QuoteDataBean getQuote(String symbol) 241 throws Exception 242 { 243 244 if (Log.doTrace()) Log.trace("TradeBean:getQuote", symbol); 245 QuoteDataBean quoteData; 246 try { 247 LocalQuote quote = quoteHome.findByPrimaryKey(symbol); 248 quoteData = quote.getDataBean(); 249 } catch (FinderException fe) { 250 //Cannot find quote for given symbol 251 Log.error("TradeBean:getQuote--> Symbol: " + symbol + " cannot be found"); 252 BigDecimal z = new BigDecimal(0.0); 253 quoteData = new QuoteDataBean("Error: Symbol " + symbol + " not found", "", 0.0, z, z, z, z, 0.0 ); 254 } 255 return quoteData; 256 } 257 258 public Collection getAllQuotes() 259 throws Exception 260 { 261 if (Log.doTrace()) Log.trace("TradeBean:getAllQuotes"); 262 263 Collection quoteBeans = new ArrayList(); 264 265 try { 266 Collection quotes = quoteHome.findAll(); 267 for (Iterator it = quotes.iterator(); it.hasNext(); ) { 268 LocalQuote quote = (LocalQuote)it.next(); 269 quoteBeans.add(quote.getDataBean()); 270 } 271 } 272 catch (FinderException fe) { 273 Log.error("TradeBean:getAllQuotes"); 274 } 275 return quoteBeans; 276 } 277 278 public QuoteDataBean updateQuotePriceVolume(String symbol, BigDecimal changeFactor, double sharesTraded) 279 throws Exception 280 { 281 282 if ( TradeConfig.getUpdateQuotePrices() == false ) 283 return new QuoteDataBean(); 284 285 if (Log.doTrace()) 286 Log.trace("TradeBean:updateQuote", symbol, changeFactor); 287 288 QuoteDataBean quoteData; 289 try { 290 LocalQuote quote = quoteHome.findByPrimaryKeyForUpdate(symbol); 291 BigDecimal oldPrice = quote.getPrice(); 292 293 if (quote.getPrice().equals(TradeConfig.PENNY_STOCK_PRICE)) { 294 changeFactor = TradeConfig.PENNY_STOCK_RECOVERY_MIRACLE_MULTIPLIER; 295 } 296 297 BigDecimal newPrice = changeFactor.multiply(oldPrice).setScale(2, BigDecimal.ROUND_HALF_UP); 298 299 quote.updatePrice(newPrice); 300 quote.addToVolume(sharesTraded); 301 quoteData = quote.getDataBean(); 302 ((Trade)context.getEJBObject()).publishQuotePriceChange(quoteData, oldPrice, changeFactor, sharesTraded); 303 304 } catch (FinderException fe) { 305 //Cannot find quote for given symbol 306 Log.error("TradeBean:updateQuotePriceVolume--> Symbol: " + symbol + " cannot be found"); 307 quoteData = new QuoteDataBean("Error: Symbol " + symbol + " not found"); 308 } 309 return quoteData; 310 } 311 312 public void publishQuotePriceChange(QuoteDataBean quoteData, BigDecimal oldPrice, BigDecimal changeFactor, double sharesTraded) 313 throws RemoteException 314 { 315 if ( publishQuotePriceChange == false) 316 return; 317 if (Log.doTrace()) 318 Log.trace("TradeBean:publishQuotePricePublishing -- quoteData = " + quoteData); 319 320 Connection conn = null; 321 Session sess = null; 322 323 try 324 { 325 conn = tConnFactory.createConnection(); 326 sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); 327 MessageProducer msgProducer = sess.createProducer(streamerTopic); 328 TextMessage message = sess.createTextMessage(); 329 330 String command = "updateQuote"; 331 message.setStringProperty("command", command); 332 message.setStringProperty("symbol", quoteData.getSymbol() ); 333 message.setStringProperty("company", quoteData.getCompanyName() ); 334 message.setStringProperty("price", quoteData.getPrice().toString()); 335 message.setStringProperty("oldPrice",oldPrice.toString()); 336 message.setStringProperty("open", quoteData.getOpen().toString()); 337 message.setStringProperty("low", quoteData.getLow().toString()); 338 message.setStringProperty("high", quoteData.getHigh().toString()); 339 message.setDoubleProperty("volume", quoteData.getVolume()); 340 341 message.setStringProperty("changeFactor", changeFactor.toString()); 342 message.setDoubleProperty("sharesTraded", sharesTraded); 343 message.setLongProperty("publishTime", System.currentTimeMillis()); 344 message.setText("Update Stock price for " + quoteData.getSymbol() + " old price = " + oldPrice + " new price = " + quoteData.getPrice()); 345 346 msgProducer.send(message); 347 } 348 catch (Exception e) 349 { 350 throw new RemoteException(e.getMessage(),e); // pass the exception back 351 } 352 finally { 353 try{ 354 if (conn != null) 355 conn.close(); 356 if (sess != null) 357 sess.close(); 358 }catch (Exception e){ 359 throw new RemoteException(e.getMessage(),e); // pass the exception back 360 } 361 } 362 } 363 364 public OrderDataBean buy(String userID, String symbol, double quantity, int orderProcessingMode) 365 throws Exception 366 { 367 368 LocalOrder order=null; 369 BigDecimal total; 370 try { 371 if (Log.doTrace()) 372 Log.trace("TradeBean:buy", userID, symbol, new Double(quantity), new Integer(orderProcessingMode)); 373 /* The following commented code shows alternative forms of the finder needed for this 374 * method 375 * The first alternative requires a 2-table join. Some database cannot allocate an Update 376 * Lock on a join select. 377 * 378 * The second alternative shows the finder being executed without allocation an update 379 * lock on the row. Normally, an update lock would not be necessary, but is required if 380 * the same user logs in multiple times to avoid a deadlock situation. 381 * 382 * The third alternative runs the finder and allocates an update lock on the row(s) 383 * 384 LocalAccount account = accountHome.findByUserIDForUpdate(userID); 385 386 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKey(userID)).getAccountForUpdate(); 387 */ 388 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate(); 389 390 LocalQuote quote = quoteHome.findByPrimaryKey(symbol); 391 LocalHolding holding = null; //The holding will be created by this buy order 392 Integer orderID = keySequence.getNextID("order"); 393 394 order = createOrder(orderID, account, quote, holding, "buy", quantity); 395 396 //UPDATE - account should be credited during completeOrder 397 BigDecimal price = quote.getPrice(); 398 BigDecimal orderFee = order.getOrderFee(); 399 BigDecimal balance = account.getBalance(); 400 total = (new BigDecimal(quantity).multiply(price)).add(orderFee); 401 account.setBalance(balance.subtract(total)); 402 403 if (orderProcessingMode == TradeConfig.SYNCH) 404 completeOrderInternal(order.getOrderID()); 405 else if (orderProcessingMode == TradeConfig.ASYNCH) 406 // Invoke the queueOrderOnePhase method w/ TXN requires new attribute 407 // to side-step a 2-phase commit across DB and JMS access 408 queueOrder(order.getOrderID(), false); 409 else //TradeConfig.ASYNC_2PHASE 410 queueOrder(order.getOrderID(), true); 411 } 412 catch (Exception e) 413 { 414 Log.error("TradeBean:buy("+userID+","+symbol+","+quantity+") --> failed", e); 415 /* On exception - cancel the order */ 416 if (order != null) order.cancel(); 417 throw new EJBException(e); 418 } 419 return order.getDataBean(); 420 } 421 422 public OrderDataBean sell(String userID, Integer holdingID, int orderProcessingMode) 423 throws Exception 424 { 425 426 LocalOrder order=null; 427 BigDecimal total; 428 try { 429 if (Log.doTrace()) 430 Log.trace("TradeBean:sell", userID, holdingID, new Integer(orderProcessingMode)); 431 432 /* Some databases cannot allocate an update lock on a JOIN 433 * use the second approach below to acquire update lock 434 LocalAccount account = accountHome.findByUserIDForUpdate(userID); 435 */ 436 437 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate(); 438 LocalHolding holding; 439 try { 440 holding = holdingHome.findByPrimaryKeyForUpdate(holdingID); 441 } 442 catch (ObjectNotFoundException oe) 443 { 444 Log.error("TradeBean:sell User " + userID + " attempted to sell holding " + holdingID + " which has already been sold"); 445 OrderDataBean orderData = new OrderDataBean(); 446 orderData.setOrderStatus("cancelled"); 447 return orderData; 448 } 449 LocalQuote quote = holding.getQuote(); 450 double quantity = holding.getQuantity(); 451 Integer orderID = keySequence.getNextID("order"); 452 order = createOrder(orderID, account, quote, holding, "sell", quantity); 453 454 //UPDATE the holding purchase data to signify this holding is "inflight" to be sold 455 // -- could add a new holdingStatus attribute to holdingEJB 456 holding.setPurchaseDate(new java.sql.Timestamp(0)); 457 458 //UPDATE - account should be credited during completeOrder 459 BigDecimal price = quote.getPrice(); 460 BigDecimal orderFee = order.getOrderFee(); 461 BigDecimal balance = account.getBalance(); 462 total = (new BigDecimal(quantity).multiply(price)).subtract(orderFee); 463 account.setBalance(balance.add(total)); 464 465 if (orderProcessingMode == TradeConfig.SYNCH) 466 completeOrderInternal(order.getOrderID()); 467 else if (orderProcessingMode == TradeConfig.ASYNCH) 468 queueOrder(order.getOrderID(), false); 469 else //TradeConfig.ASYNC_2PHASE 470 queueOrder(order.getOrderID(), true); 471 472 } 473 catch (Exception e) 474 { 475 Log.error("TradeBean:sell("+userID+","+holdingID+") --> failed", e); 476 if (order != null) order.cancel(); 477 //UPDATE - handle all exceptions like: 478 throw new EJBException("TradeBean:sell("+userID+","+holdingID+")",e); 479 } 480 return order.getDataBean(); 481 } 482 483 484 public Collection getOrders(String userID) 485 throws FinderException, Exception 486 { 487 488 if (Log.doTrace()) 489 Log.trace("TradeBean:getOrders", userID); 490 491 /* The following commented code shows alternative forms of the finder needed for this 492 * method 493 * The first alternative requires a 2-table join. Some database cannot allocate an Update 494 * Lock on a join select. 495 * 496 * The second alternative shows the finder being executed without allocation an update 497 * lock on the row. Normally, an update lock would not be necessary, but is required if 498 * the same user logs in multiple times to avoid a deadlock situation. 499 * 500 * The third alternative runs the finder and allocates an update lock on the row(s) 501 * 502 503 Collection orders = accountHome.findByUserID(userID).getOrders(); 504 505 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKey(userID)).getAccountForUpdate(); 506 */ 507 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate(); 508 Collection orders = account.getOrders(); 509 510 ArrayList dataBeans = new ArrayList(); 511 if ( orders == null ) return dataBeans; 512 513 Iterator it = orders.iterator(); 514 //TODO: return top 5 orders for now -- next version will add a getAllOrders method 515 // also need to get orders sorted by order id descending 516 int i=0; 517 while ( (it.hasNext()) && (i++ < 5)) 518 dataBeans.add(((LocalOrder) it.next()).getDataBean()); 519 520 return dataBeans; 521 } 522 523 public Collection getClosedOrders(String userID) 524 throws FinderException, Exception 525 { 526 if (Log.doTrace()) 527 Log.trace("TradeBean:getClosedOrders", userID); 528 529 ArrayList dataBeans = new ArrayList(); 530 try { 531 532 /* The following commented code shows alternative forms of the finder needed for this 533 * method 534 * The first alternative requires a 2-table join. Some database cannot allocate an Update 535 * Lock on a join select. 536 * 537 * The second alternative shows the finder being executed without allocation an update 538 * lock on the row. Normally, an update lock would not be necessary, but is required if 539 * the same user logs in multiple times to avoid a deadlock situation. 540 * 541 * The third alternative runs the finder and allocates an update lock on the row(s) 542 * 543 Collection orders = orderHome.findClosedOrdersForUpdate(userID); 544 * 545 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKey(userID)).getAccount(); 546 */ 547 548 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate(); 549 //Get the primary keys for all the closed Orders for this account. 550 Collection ordersKeys = account.getClosedOrders(); 551 if ( ordersKeys == null ) return dataBeans; 552 553 Iterator it = ordersKeys.iterator(); 554 while (it.hasNext()) 555 { 556 Integer orderKey = (Integer) it.next(); 557 LocalOrder order = (LocalOrder) orderHome.findByPrimaryKeyForUpdate(orderKey); 558 //Complete the order 559 order.setOrderStatus("completed"); 560 dataBeans.add(order.getDataBean()); 561 } 562 563 } 564 catch (Exception e) 565 { 566 Log.error("TradeBean.getClosedOrders", e); 567 throw new EJBException("TradeBean.getClosedOrders - error", e); 568 } 569 return dataBeans; 570 } 571 572 573 public OrderDataBean completeOrder(Integer orderID, boolean twoPhase) 574 throws Exception { 575 if (Log.doTrace()) Log.trace("TradeBean:completeOrder", orderID + " twoPhase="+twoPhase); 576 if (twoPhase) 577 return completeOrderInternal(orderID); 578 else 579 { 580 // invoke the completeOrderOnePhase -- which requires a new transaction 581 // the completeOrder will run in it's own transaction thus not requiring a 582 // 2-phase commit 583 return ((Trade)context.getEJBObject()).completeOrderOnePhase(orderID); 584 } 585 } 586 587 //completeOrderOnePhase method is deployed w/ TXN_REQUIRES_NEW 588 //thus the completeOrder call from the MDB should not require a 2-phase commit 589 public OrderDataBean completeOrderOnePhase(Integer orderID) 590 throws RemoteException { 591 try{ 592 if (Log.doTrace()) Log.trace("TradeBean:completeOrderOnePhase", orderID); 593 return completeOrderInternal(orderID); 594 }catch (Exception e){ 595 throw new RemoteException(e.getMessage(),e); 596 } 597 } 598 599 private OrderDataBean completeOrderInternal(Integer orderID) 600 throws Exception 601 { 602 603 LocalOrder order = orderHome.findByPrimaryKeyForUpdate(orderID); 604 605 if (order == null) 606 { 607 Log.error("TradeBean:completeOrderInternal -- Unable to find Order " + orderID + " FBPK returned " + order); 608 order.cancel(); 609 return order.getDataBean(); 610 } 611 612 String orderType = order.getOrderType(); 613 String orderStatus = order.getOrderStatus(); 614 615 if (order.isCompleted()) 616 throw new EJBException("Error: attempt to complete Order that is already completed\n" + order); 617 618 LocalAccount account = order.getAccount(); 619 LocalQuote quote = order.getQuote(); 620 LocalHolding holding = order.getHoldingForUpdate(); 621 BigDecimal price = order.getPrice(); 622 double quantity = order.getQuantity(); 623 BigDecimal orderFee = order.getOrderFee(); 624 625 BigDecimal balance = account.getBalance(); 626 627 /* 628 * getProfile is marked as Pess. Update to get a DB lock 629 * Here we invoke getProfileForRead which is deployed to not 630 * lock the DB (Pess. Read) 631 */ 632 String userID = account.getProfile().getUserID(); 633 634 /* 635 * total = (quantity * purchasePrice) + orderFee 636 */ 637 638 639 if (Log.doTrace()) Log.trace( 640 "TradeBeanInternal:completeOrder--> Completing Order " + order.getOrderID() 641 + "\n\t Order info: " + order 642 + "\n\t Account info: " + account 643 + "\n\t Quote info: " + quote 644 + "\n\t Holding info: " + holding); 645 646 647 if (order.isBuy()) { 648 /* Complete a Buy operation 649 * - create a new Holding for the Account 650 * - deduct the Order cost from the Account balance 651 */ 652 653 LocalHolding newHolding = createHolding(account, quote, quantity, price); 654 order.setHolding(newHolding); 655 } 656 657 if (order.isSell()) { 658 /* Complete a Sell operation 659 * - remove the Holding from the Account 660 * - deposit the Order proceeds to the Account balance 661 */ 662 if ( holding == null ) 663 { 664 Log.error("TradeBean:completeOrderInternal -- Unable to sell order " + order.getOrderID() + " holding already sold"); 665 order.cancel(); 666 return order.getDataBean(); 667 } 668 else 669 { 670 holding.remove(); 671 holding = null; 672 } 673 674 // This is managed by the container 675 // order.setHolding(null); 676 677 } 678 order.setOrderStatus("closed"); 679 680 order.setCompletionDate(new java.sql.Timestamp(System.currentTimeMillis())); 681 682 if (Log.doTrace()) Log.trace( 683 "TradeBean:completeOrder--> Completed Order " + order.getOrderID() 684 + "\n\t Order info: " + order 685 + "\n\t Account info: " + account 686 + "\n\t Quote info: " + quote 687 + "\n\t Holding info: " + holding); 688 689 BigDecimal priceChangeFactor = TradeConfig.getRandomPriceChangeFactor(); 690 if(Log.doTrace()) 691 Log.trace("Calling TradeAction:orderCompleted from Session EJB using Session Object"); 692 //FUTURE All getEJBObjects could be local -- need to add local I/F 693 694 TradeServices trade = (TradeServices)context.getEJBObject(); 695 TradeAction tradeAction = new TradeAction(trade); 696 697 //signify this order for user userID is complete 698 tradeAction.orderCompleted(userID, orderID); 699 return order.getDataBean(); 700 } 701 702 703 //These methods are used to provide the 1-phase commit runtime option for TradeDirect 704 // Basically these methods are deployed as txn requires new and invoke TradeDirect methods 705 // There is no mechanism outside of EJB to start a new transaction 706 public OrderDataBean completeOrderOnePhaseDirect(Integer orderID) 707 throws RemoteException { 708 try{ 709 if (Log.doTrace()) 710 Log.trace("TradeBean:completeOrderOnePhaseDirect -- completing order by calling TradeDirect orderID=" +orderID); 711 return (new org.apache.geronimo.samples.daytrader.direct.TradeDirect()).completeOrderOnePhase(orderID); 712 } catch (Exception e){ 713 throw new RemoteException(e.getMessage(),e); 714 } 715 } 716 public void cancelOrderOnePhaseDirect(Integer orderID) 717 throws RemoteException { 718 try{ 719 if (Log.doTrace()) 720 Log.trace("TradeBean:cancelOrderOnePhaseDirect -- cancelling order by calling TradeDirect orderID=" +orderID); 721 (new org.apache.geronimo.samples.daytrader.direct.TradeDirect()).cancelOrderOnePhase(orderID); 722 } catch(Exception e){ 723 throw new RemoteException(e.getMessage(),e); 724 } 725 } 726 727 728 public void cancelOrder(Integer orderID, boolean twoPhase) 729 throws Exception { 730 if (Log.doTrace()) Log.trace("TradeBean:cancelOrder", orderID + " twoPhase="+twoPhase); 731 if (twoPhase) 732 cancelOrderInternal(orderID); 733 else 734 { 735 // invoke the cancelOrderOnePhase -- which requires a new transaction 736 // the completeOrder will run in it's own transaction thus not requiring a 737 // 2-phase commit 738 ((Trade)context.getEJBObject()).cancelOrderOnePhase(orderID); 739 } 740 } 741 742 //cancelOrderOnePhase method is deployed w/ TXN_REQUIRES_NEW 743 //thus the completeOrder call from the MDB should not require a 2-phase commit 744 public void cancelOrderOnePhase(Integer orderID) 745 throws RemoteException { 746 try{ 747 if (Log.doTrace()) Log.trace("TradeBean:cancelOrderOnePhase", orderID); 748 cancelOrderInternal(orderID); 749 } catch (Exception e){ 750 throw new RemoteException(e.getMessage(),e); 751 } 752 } 753 754 755 private void cancelOrderInternal(Integer orderID) 756 throws Exception 757 { 758 LocalOrder order = orderHome.findByPrimaryKeyForUpdate(orderID); 759 order.cancel(); 760 } 761 762 763 public void orderCompleted(String userID, Integer orderID) 764 throws Exception 765 { 766 throw new UnsupportedOperationException("TradeBean:orderCompleted method not supported"); 767 } 768 public LocalHolding createHolding( 769 LocalAccount account, 770 LocalQuote quote, 771 double quantity, 772 BigDecimal purchasePrice) 773 throws Exception 774 { 775 LocalHolding newHolding = null; 776 Integer holdingID = null; 777 try { 778 779 if (Log.doTrace()) 780 Log.trace("TradeBean:createHolding"); 781 782 holdingID = keySequence.getNextID("holding"); 783 newHolding = 784 holdingHome.create(holdingID, account, quote, quantity, purchasePrice); 785 } catch (Exception e) { 786 String error = "Failed to create Holding for account: " + account.getAccountID() 787 + " with quote: " + quote.getSymbol() 788 + " holdingID: " + holdingID 789 + " quantity: " + quantity + "\n"; 790 Log.error(e, error ); 791 throw new EJBException(error,e); 792 } 793 return newHolding; 794 } 795 796 public Collection getHoldings(String userID) 797 throws FinderException, Exception 798 { 799 if (Log.doTrace()) 800 Log.trace("TradeBean:getHoldings", userID); 801 Collection holdings = holdingHome.findByUserID(userID); 802 if (Log.doTrace()) 803 Log.trace("Got holdings collection size="+holdings.size()); 804 ArrayList dataBeans = new ArrayList(); 805 if ( holdings == null ) return dataBeans; 806 807 Iterator it = holdings.iterator(); 808 while (it.hasNext()) { 809 HoldingDataBean holdingData = ((LocalHolding) it.next()).getDataBean(); 810 dataBeans.add(holdingData); 811 } 812 return dataBeans; 813 } 814 815 public HoldingDataBean getHolding(Integer holdingID) 816 throws FinderException, Exception 817 { 818 if (Log.doTrace()) 819 Log.trace("TradeBean:getHolding", holdingID); 820 LocalHolding holding = holdingHome.findByPrimaryKey(holdingID); 821 HoldingDataBean holdingData = holding.getDataBean(); 822 if (Log.doTrace()) 823 Log.trace("TradeBean:getHolding " + holdingData); 824 return holdingData; 825 } 826 827 828 829 public LocalOrder createOrder( 830 int orderID, 831 LocalAccount account, 832 LocalQuote quote, 833 LocalHolding holding, 834 String orderType, 835 double quantity) 836 throws Exception { 837 try 838 { 839 return createOrder(new Integer(orderID), account, quote, holding, orderType, quantity); 840 } 841 catch(Exception e) 842 { 843 Log.error("TradeBean:createOrder -- failed to create Order", e); 844 throw new EJBException("TradeBean:createOrder -- failed to create Order", e); 845 } 846 } 847 848 public LocalOrder createOrder( 849 Integer orderID, 850 LocalAccount account, 851 LocalQuote quote, 852 LocalHolding holding, 853 String orderType, 854 double quantity) 855 throws CreateException, Exception { 856 857 LocalOrder order = null; 858 859 if (Log.doTrace() ) 860 Log.trace( 861 "TradeBean:createOrder(orderID=" 862 + orderID 863 + " account=" 864 + ((account == null) ? null : account.getPrimaryKey()) 865 + " quote=" 866 + ((quote == null) ? null : quote.getPrimaryKey()) 867 + " orderType=" 868 + orderType 869 + " quantity=" 870 + quantity); 871 try 872 { 873 order = orderHome.create(orderID, account, quote, holding, orderType, quantity); 874 } 875 catch(Exception e) 876 { 877 Log.error("TradeBean:createOrder -- failed to create Order", e); 878 throw new EJBException("TradeBean:createOrder -- failed to create Order", e); 879 } 880 return order; 881 } 882 883 public AccountDataBean login(String userID, String password) 884 throws FinderException, Exception { 885 886 /* The following commented code shows alternative forms of the finder needed for this 887 * method 888 * The first alternative requires a 2-table join. Some database cannot allocate an Update 889 * Lock on a join select. 890 * 891 * The second alternative shows the finder being executed without allocation an update 892 * lock on the row. Normally, an update lock would not be necessary, but is required if 893 * the same user logs in multiple times to avoid a deadlock situation. 894 * 895 * The third alternative runs the finder and allocates an update lock on the row(s) 896 * 897 LocalAccount account = accountHome.findByUserIDForUpdate(userID); 898 899 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKey(userID)).getAccountForUpdate(); 900 */ 901 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate(); 902 903 if (Log.doTrace()) 904 Log.trace("TradeBean:login", userID, password); 905 account.login(password); 906 AccountDataBean accountData = account.getDataBean(); 907 if (Log.doTrace()) 908 Log.trace("TradeBean:login(" + userID + "," + password + ") success" + accountData); 909 return accountData; 910 } 911 912 public void logout(String userID) 913 throws FinderException, Exception { 914 if (Log.doTrace()) 915 Log.trace("TradeBean:logout", userID); 916 917 /* The following commented code shows alternative forms of the finder needed for this 918 * method 919 * The first alternative requires a 2-table join. Some database cannot allocate an Update 920 * Lock on a join select. 921 * 922 * The second alternative shows the finder being executed without allocation an update 923 * lock on the row. Normally, an update lock would not be necessary, but is required if 924 * the same user logs in multiple times to avoid a deadlock situation. 925 * 926 * The third alternative runs the finder and allocates an update lock on the row(s) 927 928 * LocalAccount account = accountHome.findByUserIDForUpdate(userID); 929 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKey(userID)).getAccountForUpdate(); 930 * 931 */ 932 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate(); 933 934 935 if (Log.doTrace()) Log.trace("TradeBean:logout(" + userID + ") success"); 936 account.logout(); 937 } 938 939 public AccountDataBean register( 940 String userID, 941 String password, 942 String fullname, 943 String address, 944 String email, 945 String creditcard, 946 BigDecimal openBalance) 947 throws CreateException, Exception { 948 949 LocalAccount account = null; 950 951 try { 952 if (Log.doTrace()) 953 Log.trace("TradeBean:register", userID, password, fullname, address, email, creditcard, openBalance); 954 955 Integer accountID = keySequence.getNextID("account"); 956 account = 957 accountHome.create( 958 accountID, 959 userID, 960 password, 961 openBalance, 962 fullname, 963 address, 964 email, 965 creditcard); 966 } 967 catch (Exception e) 968 { 969 Log.error("Failed to register new Account\n" + e); 970 throw new EJBException("Failed to register new Account\n", e); 971 } 972 AccountDataBean accountData = account.getDataBean(); 973 return accountData; 974 } 975 976 public AccountDataBean getAccountData(String userID) 977 throws FinderException, RemoteException { 978 979 if (Log.doTrace()) 980 Log.trace("TradeBean:getAccountData", userID); 981 982 /* The following commented code shows alternative forms of the finder needed for this 983 * method 984 * The first alternative requires a 2-table join. Some database cannot allocate an Update 985 * Lock on a join select. 986 * 987 * The second alternative shows the finder being executed without allocation an update 988 * lock on the row. Normally, an update lock would not be necessary, but is required if 989 * the same user logs in multiple times to avoid a deadlock situation. 990 * 991 * The third alternative runs the finder and allocates an update lock on the row(s) 992 * 993 LocalAccount account = accountHome.findByUserID(userID); 994 995 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKey(userID)).getAccountForUpdate(); 996 */ 997 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate(); 998 999 AccountDataBean accountData = account.getDataBean(); 1000 return accountData; 1001 } 1002 public AccountProfileDataBean getAccountProfileData(String userID) 1003 throws FinderException, Exception { 1004 if (Log.doTrace()) 1005 Log.trace("TradeBean:getAccountProfileData", userID); 1006 1007 /* The following commented code shows alternative forms of the finder needed for this 1008 * method 1009 * The first alternative requires a 2-table join. Some database cannot allocate an Update 1010 * Lock on a join select. 1011 * 1012 * The second alternative shows the finder being executed without allocation an update 1013 * lock on the row. Normally, an update lock would not be necessary, but is required if 1014 * the same user logs in multiple times to avoid a deadlock situation. 1015 * 1016 * The third alternative runs the finder and allocates an update lock on the row(s) 1017 * 1018 LocalAccount account = accountHome.findByUserID(userID); 1019 1020 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKey(userID)).getAccountForUpdate(); 1021 */ 1022 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(userID)).getAccountForUpdate(); 1023 1024 AccountProfileDataBean accountProfileData = account.getProfileDataBean(); 1025 return accountProfileData; 1026 } 1027 1028 public AccountProfileDataBean updateAccountProfile(AccountProfileDataBean accountProfileData) 1029 throws FinderException, Exception { 1030 1031 if (Log.doTrace()) 1032 Log.trace("TradeBean:updateAccountProfileData", accountProfileData); 1033 1034 LocalAccount account = ((LocalAccountProfile) profileHome.findByPrimaryKeyForUpdate(accountProfileData.getUserID())).getAccountForUpdate(); 1035 1036 accountProfileData = 1037 account.updateAccountProfile(accountProfileData); 1038 return accountProfileData; 1039 } 1040 1041 public RunStatsDataBean resetTrade(boolean deleteAll) 1042 throws Exception 1043 { 1044 if (Log.doTrace()) 1045 Log.trace("TradeBean:resetTrade", new Boolean(deleteAll)); 1046 1047 //Clear MDB Statistics 1048 MDBStats.getInstance().reset(); 1049 // Reset Trade 1050 return new org.apache.geronimo.samples.daytrader.direct.TradeDirect().resetTrade(deleteAll); 1051 } 1052 1053 private Collection getDataBeansCollection(Collection entities) 1054 { 1055 ArrayList dataBeans = new ArrayList(); 1056 1057 if ( (entities == null) || (entities.size()<= 0) ) 1058 return dataBeans; 1059 1060 Iterator it = entities.iterator(); 1061 while (it.hasNext()) { 1062 LocalQuote entity = (LocalQuote) it.next(); 1063 Object o = (Object)entity.getDataBean(); 1064 dataBeans.add(o); 1065 } 1066 return dataBeans; 1067 } 1068 1069 /** 1070 * provides a simple session method with no database access to test performance of a simple 1071 * path through a stateless session 1072 * @param investment amount 1073 * @param NetValue current value 1074 * @return return on investment as a percentage 1075 */ 1076 public double investmentReturn(double investment, double NetValue) 1077 throws RemoteException 1078 { 1079 if (Log.doTrace()) 1080 Log.trace("TradeBean:investmentReturn"); 1081 1082 double diff = NetValue - investment; 1083 double ir = diff / investment; 1084 return ir; 1085 1086 } 1087 1088 /** 1089 * This method provides a ping test for a 2-phase commit operation 1090 * 1091 * @param symbol to lookup 1092 * @return quoteData after sending JMS message 1093 */ 1094 public QuoteDataBean pingTwoPhase(String symbol) 1095 throws RemoteException 1096 { 1097 try{ 1098 if (Log.doTrace()) Log.trace("TradeBean:pingTwoPhase", symbol); 1099 QuoteDataBean quoteData=null; 1100 Connection conn = null; 1101 Session sess = null; 1102 try { 1103 1104 //Get a Quote and send a JMS message in a 2-phase commit 1105 LocalQuote quote = quoteHome.findByPrimaryKey(symbol); 1106 quoteData = quote.getDataBean(); 1107 1108 conn = qConnFactory.createConnection(); 1109 sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); 1110 MessageProducer msgProducer = sess.createProducer(queue); 1111 TextMessage message = sess.createTextMessage(); 1112 1113 String command= "ping"; 1114 message.setStringProperty("command", command); 1115 message.setLongProperty("publishTime", System.currentTimeMillis()); 1116 message.setText("Ping message for queue java:comp/env/jms/TradeBrokerQueue sent from TradeSessionEJB:pingTwoPhase at " + new java.util.Date()); 1117 1118 msgProducer.send(message); 1119 } 1120 catch (Exception e) { 1121 Log.error("TradeBean:pingTwoPhase -- exception caught",e); 1122 } 1123 1124 finally { 1125 if (conn != null) 1126 conn.close(); 1127 if (sess != null) 1128 sess.close(); 1129 } 1130 1131 return quoteData; 1132 } catch (Exception e){ 1133 throw new RemoteException(e.getMessage(),e); 1134 } 1135 } 1136 1137 1138 /* Required javax.ejb.SessionBean interface methods */ 1139 1140 public TradeBean() { 1141 } 1142 1143 private static boolean warnJMSFailure = true; 1144 public void ejbCreate() throws CreateException { 1145 try { 1146 1147 if (Log.doTrace()) 1148 Log.trace("TradeBean:ejbCreate -- JNDI lookups of EJB and JMS resources"); 1149 1150 InitialContext ic = new InitialContext(); 1151 quoteHome = (LocalQuoteHome) ic.lookup("java:comp/env/ejb/Quote"); 1152 accountHome = (LocalAccountHome) ic.lookup("java:comp/env/ejb/Account"); 1153 profileHome = (LocalAccountProfileHome) ic.lookup("java:comp/env/ejb/AccountProfile"); 1154 holdingHome = (LocalHoldingHome) ic.lookup("java:comp/env/ejb/Holding"); 1155 orderHome = (LocalOrderHome) ic.lookup("java:comp/env/ejb/Order"); 1156 keySequenceHome = (LocalKeySequenceHome) ic.lookup("java:comp/env/ejb/KeySequence"); 1157 1158 orderBySQLSupported = ( (Boolean) ic.lookup("java:comp/env/orderBySQLSupported") ).booleanValue(); 1159 publishQuotePriceChange = ( (Boolean) ic.lookup("java:comp/env/publishQuotePriceChange") ).booleanValue(); 1160 updateQuotePrices = ( (Boolean) ic.lookup("java:comp/env/updateQuotePrices") ).booleanValue(); 1161 TradeConfig.setUpdateQuotePrices(updateQuotePrices); 1162 1163 try 1164 { 1165 qConnFactory = (ConnectionFactory) ic.lookup("java:comp/env/jms/QueueConnectionFactory"); 1166 tConnFactory = (ConnectionFactory) ic.lookup("java:comp/env/jms/TopicConnectionFactory"); 1167 streamerTopic = (Topic) ic.lookup("java:comp/env/jms/TradeStreamerTopic"); 1168 queue = (Queue) ic.lookup("java:comp/env/jms/TradeBrokerQueue"); 1169 } 1170 catch (Exception e) 1171 { 1172 if (warnJMSFailure == true) 1173 { 1174 warnJMSFailure = false; 1175 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); 1176 } 1177 publishQuotePriceChange = false; 1178 } 1179 1180 } catch (Exception e) { 1181 Log.error("TradeBean:ejbCreate: Lookup of Local Entity Homes Failed\n" + e); 1182 e.printStackTrace(); 1183 //UPDATE 1184 //throw new CreateException(e.toString()); 1185 } 1186 1187 if ((quoteHome == null) || 1188 ( accountHome == null) || 1189 ( holdingHome == null) || 1190 ( orderHome == null) || 1191 ( keySequenceHome == null) //|| 1192 // ( qConnFactory == null) || 1193 // ( queue == null) || 1194 // ( tConnFactory == null) || 1195 // ( streamerTopic == null) || 1196 ) 1197 { 1198 String error = "TradeBean:ejbCreate() JNDI lookup of Trade resource failed\n" + 1199 "\n\t quoteHome="+quoteHome+ 1200 "\n\t accountHome="+ accountHome+ 1201 "\n\t holdingHome="+ holdingHome+ 1202 "\n\t orderHome="+ orderHome+ 1203 "\n\t qConnFactory="+ qConnFactory+ 1204 "\n\t queue="+ queue+ 1205 "\n\t tConnFactory="+ tConnFactory+ 1206 "\n\t streamerTopic="+ streamerTopic; 1207 Log.error(error); 1208 //UPDATE 1209 //throw new EJBException(error); 1210 } 1211 keySequence = keySequenceHome.create(); 1212 } 1213 1214 public void ejbRemove() 1215 { 1216 try 1217 { 1218 if (Log.doTrace()) 1219 Log.trace("TradeBean:ejbRemove"); 1220 } 1221 catch (Exception e) 1222 { 1223 Log.error(e,"Unable to close Queue or Topic connection on Session EJB remove"); 1224 } 1225 } 1226 public void ejbActivate() { 1227 } 1228 public void ejbPassivate() { 1229 } 1230 1231 public void setSessionContext(SessionContext sc) { 1232 context = sc; 1233 } 1234 }