//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to You 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.
//
// .Net StockTrader Sample WCF Application for Benchmarking, Performance Analysis and Design Considerations for Service-Oriented Applications
//======================================================================================================
// The implementation class for the BSL, defining all the business logic.
//======================================================================================================
//======================================================================================================
// Code originally contributed by Microsoft Corporation.
// This contribution to the Stonehenge project is limited strictly
// to the source code that is submitted in this submission.
// Any technology, including underlying platform technology,
// that is referenced or required by the submitted source code
// is not a part of the contribution.
// For example and not by way of limitation,
// any systems/Windows libraries (WPF, WCF, ASP.NET etc.)
// required to run the submitted source code is not a part of the contribution
//======================================================================================================
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Transactions;
using System.Reflection;
using System.Configuration;
using System.Security.Policy;
using Trade.IDAL;
using Trade.DALFactory;
using Trade.BusinessServiceConfigurationSettings;
using Trade.BusinessServiceDataContract;
using Trade.BusinessServiceContract;
using Trade.Utility;
using Trade.OrderProcessorAsyncClient;
using Trade.OrderProcessorContract;
namespace Trade.BusinessServiceImplementation
{
///
/// Tradeservice.cs. This is the base service implementation class--the Business Service Layer or BSL.
/// It holds the actual implementation of the business logic for the various
/// business service methods or operations, save for the order submit and processing operations for buys and sells
/// (these are contained in the separate project/namespace SharedOrderProcessClass). Both the
/// ASMX and WCF Service facades flow through this class by exposing a service interface to this logic.
/// This class makes calls to the separate, logical data access layer (DAL). It activates the database
/// layer via a factory model, loading a specific DAL on demand, vs direcly referencing
/// it in the VS project. This helps to further 'decouple' the BSL from the underlying database it operates against.
///
public class TradeService : ITradeServices
{
private IOrder dalOrder;
private ICustomer dalCustomer;
private IMarketSummary dalMarketSummary;
//The following are the core functional service method implementations that
//provide for the business functions/back-end processing of the application.
//Each has a counterpart service to the Trade 6.1 WebSphere benchmark application.
//These methods are exposed as industry-standard Web Services via both
//ASMX and Windows Communication Foundation (.NET 3.5) facades.
//As such, the J2EE/WebSphere front end can easily be used with the
//.NET backend (and vice-versa); simply by configuring the WCF service endpoints.
//The more interesting business logic functions are buy, sell and register new
//accounts, since these actually have some business logic required, and transactions
//involved.
//Another important point is the use of a (quite simple, actually) DAL technique that enables
//the BSL to be more effectively isolated from the DAL. Read the technical documentation for more
//information. The key here is that our DAL overcomes some previous limitations (for example, see PetShop
//3.0 or 4.0) for enterprise applications that had multiple DAL method calls within a single business logic
//operation yet did not need to or want to invoke a distributed transaction against SQL Server since all operations
//would be against a single database resource. Our new DAL accomplishes this without having
//to pass transaction objects or connections between layers. At the same time, it enables the
//SQL Server 2005 DAL to fully utilize System.Transaction 'lightweight' transaction feature without
//extra code. Newer releases of the Oracle Data Provider for .NET (ODP.NET) also support this feature when using
//Oracle database with the Trade.DAL.Oracle Data Access Layer.
public void emptyMethodAction()
{
return;
}
public void isOnline()
{
return;
}
///
/// Logs user in/authenticates against StockTrader database.
///
/// User id to authenticate.
/// Password for authentication
public AccountDataModel login(string userid, string password)
{
Console.WriteLine("Entering Login");
//Create instance of a DAL, which could be designed for any type of DB backend.
dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL);
//As feature of the StockTrader DAL, you will see dal.Open, dal.BeginTransaction, dal.CommitTransaction,
//dal.AbortTransaction and dal.Close methods being invoked in the BSL. The pattern within this BSL is:
//a) Create an instance of the DAL;
//b) Open the DAL;
//c) Start a transaction only if necessary (more than one update/insert/delete involved);
//d) You get to pick ADO.NET transaction or System.Transactions or ServicedComponent, it will work with
// all of the above; StockTrader lets you choose ADO.NET txs or System.Transactions via config.
//e) Close the DAL. This releases the DAL's internal connection back to the connection pool.
//The implementation "hides" the type of database being used from the BSL, so this BSL will work
//with any type of database you create a DAL for, with no changes in the BSL whatsoever.
//System.Transactions and SQL Server 2005 and Oracle databases work together
//with a new feature called "lightweight transactions"; which means you do not *need* to have the
//same performance penalty you got with Serviced Components for always invoking the tx as a full
//two-phase operation with DTC logging unless necessary. For now, only SQL Server 2005 and Oracle databases support this feature which
//will automatically keep a transaction as a local database transaction and auto-promote to a distributed tx if needed.
//If operating against a single connection to SQL 2005 or Oracle, will not promote to a DTC-coordinated tx; and hence be much faster.
//On other databases, System.Transactions (and Serviced Components) *always* invoke the DTC, even when
//on a single connection to a single database. So our StockTrader DAL is designed to:
// 1. Hide DB implementation from BSL so we maintain clean separation of BSL from DAL.
// 2. Let you freely call into the DAL from BSL methods as many times as you want *without*
// creating new separate DB connections; which would invoke the DTC even on SQL Server 2005.
// 3. As a by-product, it also helps you use ADO.NET transactions without worrying about
// passing DB connections/transaction objects between tiers; maintaining cleaner separation
// of BSL from DAL. If using ADO.NET txs; you can accomplish DB-implementation isolation also with
// the Provider Factories introduced with ADO.NET 2.0/.NET 2.0: see for details:
// http://msdn2.microsoft.com/en-us/library/ms379620(VS.80).aspx
//Note Open() is not really necessary, since the DAL will open a new connection automatically
//if it's internal connection is not already open. It's also free to open up more connections, if desired.
//We use Open() to stick with a consistent pattern in this application, since the Close() method IS
//important. Look for this pattern in all BSL methods below; with a transaction scope defined
//only for operations that actually require a transaction per line (c) above.
dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);
try
{
Console.WriteLine("Leaving Login");
return dalCustomer.login(userid, password);
}
catch
{
throw;
}
finally
{
//Always close the DAL, this releases its primary DB connection.
dalCustomer.Close();
}
}
///
/// Logs a user out--updates logout count.
///
/// User id to logout.
public void logout(string userID)
{
dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL);
dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);;
try
{
dalCustomer.logOutUser(userID);
return;
}
catch
{
throw;
}
finally
{
dalCustomer.Close();
}
}
///
/// Gets account data for a user. Transforms data from DataContract to model UI class for HTML display.
///
/// User id to retrieve data for.
public AccountDataModel getAccountData(string userID)
{
dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL);
dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);;
try
{
return dalCustomer.getCustomerByUserID(userID);
}
catch
{
throw;
}
finally
{
dalCustomer.Close();
}
}
///
/// Gets account profile data for a user. Transforms data from DataContract to model UI class for HTML display.
///
/// User id to retrieve data for.
public AccountProfileDataModel getAccountProfileData(string userID)
{
dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL);
dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);;
try
{
return dalCustomer.getAccountProfileData(userID);
}
catch
{
throw;
}
finally
{
dalCustomer.Close();
}
}
///
/// Gets recent orders for a user. Transforms data from DataContract to model UI class for HTML display.
///
/// User id to retrieve data for.
public List getOrders(string userID)
{
dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL);
dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);;
try
{
return dalCustomer.getOrders(userID, false, Settings.MAX_QUERY_TOP_ORDERS,Settings.MAX_QUERY_ORDERS);
}
catch
{
throw;
}
finally
{
dalCustomer.Close();
}
}
///
/// Gets specific top n orders for a user. Transforms data from DataContract to model UI class for HTML display.
///
/// User id to retrieve data for.
public List getTopOrders(string userID)
{
dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL);
dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);;
try
{
return dalCustomer.getOrders(userID, true, Settings.MAX_QUERY_TOP_ORDERS,Settings.MAX_QUERY_ORDERS);
}
catch
{
throw;
}
finally
{
dalCustomer.Close();
}
}
///
/// Gets any closed orders for a user--orders that have been processed. Also updates status to complete.
///
/// User id to retrieve data for.
public List getClosedOrders(string userID)
{
dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL);
dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);;
try
{
return dalCustomer.getClosedOrders(userID);
}
catch
{
throw;
}
finally
{
dalCustomer.Close();
}
}
///
/// Gets holding data for a user. Transforms data from DataContract to model UI class for HTML display.
///
/// User id to retrieve data for.
public List getHoldings(string userID)
{
dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL);
dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);;
try
{
return dalCustomer.getHoldings(userID);
}
catch
{
throw;
}
finally
{
dalCustomer.Close();
}
}
///
/// Gets a holding for a user. Transforms data from DataContract to model UI class for HTML display.
///
/// User id to retrieve data for.
/// Holding id to retrieve data for.
public HoldingDataModel getHolding(string userID, int holdingID)
{
dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL);
dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);;
try
{
return dalCustomer.getHolding(userID, holdingID);
}
catch
{
throw;
}
finally
{
dalCustomer.Close();
}
}
///
/// Registers/adds new user to database.
///
/// User id for account creation/login purposes as specified by user.
/// Password as specified by user.
/// Name as specified by user.
/// Address as specified by user.
/// Email as specified by user.
/// Credit card number as specified by user.
/// Open balance as specified by user.
public AccountDataModel register(string userID, string password, string fullname, string address, string email, string creditcard, decimal openBalance)
{
dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL);
//Switch is two let you configure which transaction model you want to benchmark/test.
switch (Settings.TRANSACTION_MODEL)
{
case (StockTraderUtility.TRANSACTION_MODEL_SYSTEMDOTTRANSACTION_TRANSACTION):
{
System.Transactions.TransactionOptions txOps = new TransactionOptions();
txOps.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
txOps.Timeout = TimeSpan.FromSeconds(15);
//Start our System.Transactions tx with the options set above. System.Transactions
//will handle rollbacks automatically if there is an exception; note the
//difference between the System.Transaction case and the ADO.NET transaction case;
//and where the dal.Open() happens (which opens a 'hidden' DB connection in DAL).
//System.Transactions will automatically enlist ANY connection
//opened within the tx scope in the transaction for you. Since it supports distributed
//tx's; it frees you quite a bit, with the caveat of the overhead of doing a distributed
//tx when you do not need one. Hence: lightweight System.Transactions with SQL Server 2005 and Oracle;
//and our DAL designed to take advantage of this important feature (auto-promote to DTC
//only if needed). ADO.NET txs always require an already-open connection before starting a tx.
using (TransactionScope tx = new TransactionScope(TransactionScopeOption.Required, txOps))
{
//Now open the connection, after entering tx scope.
dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);;
try
{
AccountDataModel newCustomer = addNewRegisteredUser(userID, password, fullname, address, email, creditcard, openBalance);
//Scope complete, commit work.
tx.Complete();
return newCustomer;
}
catch
{
//no rollback needed, infrastructure will never commit without
//scope.Complete() and immediately issue rollback on and unhandled
//exception.
throw;
}
finally
{
dalCustomer.Close();
}
}
}
case (StockTraderUtility.TRANSACTION_MODEL_ADONET_TRANSACTION):
{
//ADO.NET TX case: First you need to open the connecton.
dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);;
//Now you start TX
dalCustomer.BeginADOTransaction();
try
{
AccountDataModel newCustomer = addNewRegisteredUser(userID, password, fullname, address, email, creditcard, openBalance);
//done, commit.
dalCustomer.CommitADOTransaction();
return newCustomer;
}
catch
{
//explicit rollback needed.
dalCustomer.RollBackTransaction();
throw;
}
finally
{
//ALWAYS call dal.Close is using StockTrader DAL implementation;
//this is equivalent to calling Connection.Close() in the DAL --
//but for a generic DB backend as far as the BSL is concerned.
dalCustomer.Close();
}
}
}
throw new Exception(Settings.ENABLE_GLOBAL_SYSTEM_DOT_TRANSACTIONS_CONFIGSTRING + ": " + StockTraderUtility.EXCEPTION_MESSAGE_INVALID_TXMMODEL_CONFIG + " Repository ConfigKey table.");
}
///
/// Adds user account data to Account table and also profile data to AccountProfile table.
///
///
///
///
///
///
///
///
///
private AccountDataModel addNewRegisteredUser(string userID, string password, string fullname, string address, string email, string creditcard, decimal openBalance)
{
AccountProfileDataModel customerprofile = new AccountProfileDataModel(userID, password, fullname, address, email, creditcard);
dalCustomer.insertAccountProfile(customerprofile);
//Check our acid test conditions here for transactional testing; we want to test part way through
//the register operations from the BSL, to make sure database is never left in state with one
//insert above going through, and the one below not--the entire BSL operation needs to be
//treated as one logical unit of work. Also note the ordering of operations here:
//since trying to register a non-unique userid might be something that happens frequently in the real
//world, lets do the insert that would fail on this condition first (accountprofile);
//rather than wait and do it last.
if (customerprofile.userID.Equals(StockTraderUtility.ACID_TEST_USER))
throw new Exception(StockTraderUtility.EXCEPTION_MESSAGE_ACID_REGISTRATION);
AccountDataModel customer = new AccountDataModel(0, userID, DateTime.Now, (decimal)openBalance, 0, (decimal)openBalance, DateTime.Now, 0);
dalCustomer.insertAccount(customer);
return customer;
}
///
/// Updates account profile data for a user.
///
/// Profile data model class with updated info.
public AccountProfileDataModel updateAccountProfile(AccountProfileDataModel profileData)
{
dalCustomer = Trade.DALFactory.Customer.Create(Settings.DAL);
dalCustomer.Open(Settings.TRADEDB_SQL_CONN_STRING);;
try
{
return dalCustomer.update(profileData);
}
catch
{
throw;
}
finally
{
dalCustomer.Close();
}
}
///
/// Gets the current market summary. This results in an expensive DB query in the DAL; hence look to cache data returned for 60 second or so.
///
public MarketSummaryDataModelWS getMarketSummary()
{
dalMarketSummary = Trade.DALFactory.MarketSummary.Create(Settings.DAL);
dalMarketSummary.Open(Settings.TRADEDB_SQL_CONN_STRING);
try
{
return dalMarketSummary.getMarketSummaryData();
}
catch
{
throw;
}
finally
{
dalMarketSummary.Close();
}
}
///
/// Gets a single quote based on symbol.
///
/// Symbol to get data for.
public QuoteDataModel getQuote(string symbol)
{
dalMarketSummary = Trade.DALFactory.MarketSummary.Create(Settings.DAL);
dalMarketSummary.Open(Settings.TRADEDB_SQL_CONN_STRING);
try
{
return dalMarketSummary.getQuote(symbol);
}
catch
{
throw;
}
finally
{
dalMarketSummary.Close();
}
}
///
/// Performs a holding sell operation.
/// Note orderProcessing mode param is not used by StockTrader or IBM Trade 6.1; instead
/// both apps pick this up from the application configuration.
///
/// User id to create/submit order for.
/// Holding id to sell off.
/// Not used, set to zero.
public OrderDataModel sell(string userID, int holdingID, int orderProcessingMode)
{
//In the case of running in 'Sync_InProcess' mode, then the PlaceOrder method
//will synchronously invoke the processOrder method as part of this service, and not make an
//additional remote service-to-sevice call via WCF. See ProcessOrder.cs.
//note, this method always sells entire holding, quantity is not passed in. This is default behavior of WebSphere Trade 6.1
return placeOrder(StockTraderUtility.ORDER_TYPE_SELL, userID, holdingID, null, (double)0);
}
///
/// Allows user to sell part of a holding vs. all.
/// This is added functionality that .NET StockTrader implements to allow selling of partial
/// holdings, vs. liquidating the entire holding at once, ala IBM Trade 6.1. This method is
/// exposed by StockTrader as a WCF/Web service; but does not exist in IBM Trade 6.1.
///
/// User id to submit sell for.
/// Holding id to sell.
/// Number of shares to sell.
public OrderDataModel sellEnhanced(string userID, int holdingID, double quantity)
{
return placeOrder(StockTraderUtility.ORDER_TYPE_SELL_ENHANCED, userID, holdingID, null, quantity);
}
///
/// Performs a stock buy operation.
/// Note orderProcessing mode param is not used by StockTrader or IBM Trade 6.1; instead
/// both apps pick this up from the application configuration.
///
/// User id to create/submit order for.
/// Stock symbol to buy.
/// Shares to buy.
///Not used.
public OrderDataModel buy(string userID, string symbol, double quantity, int orderProcessingMode)
{
return placeOrder(StockTraderUtility.ORDER_TYPE_BUY, userID, 0, symbol, quantity);
}
///
/// This is the business logic for placing orders, handling txs.
/// StockTrader allows the user to select whether to use the new .NET 2.0 System.Transactions
/// or use ADO.NET Provider-supplied transactions for order placement (buy, sell); and new user registrations.
/// The best choice for performance will vary based on the backend database being used with the
/// application.
///
/// Buy or sell type.
/// User id to create/submit order for.
/// Holding id to sell.
/// Stock symbol to buy.
/// Shares to buy.
public OrderDataModel placeOrder(string orderType, string userID, int holdingID, string symbol, double quantity)
{
OrderDataModel order = null;
HoldingDataModel holding = new HoldingDataModel();
dalOrder = Trade.DALFactory.Order.Create(Settings.DAL);
switch (Settings.TRANSACTION_MODEL)
{
case (StockTraderUtility.TRANSACTION_MODEL_SYSTEMDOTTRANSACTION_TRANSACTION):
{
System.Transactions.TransactionOptions txOps = new TransactionOptions();
txOps.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;
txOps.Timeout = TimeSpan.FromSeconds(Settings.SYSTEMDOTTRANSACTION_TIMEOUT);
//Start our System.Transactions tx with the options set above.
using (TransactionScope tx = new TransactionScope(TransactionScopeOption.Required, txOps))
{
dalOrder.Open(Settings.TRADEDB_SQL_CONN_STRING);
try
{
//Business Step 1: create the order header.
order = createOrder(orderType, userID, holdingID, symbol, quantity, ref holding);
//Business Step 2: Determine which order processing mode to use,
//and either process order right away (sync) and in-process with
//calling ASP.NET Web app; or interface with the
//async WCF Order Processing Service (cooler) via a service-to-service call,
//distributed/remote.
if (Settings.orderMode != StockTraderUtility.ORDER_Sync)
{
//Fire up our async client; we follow the same model here as with the
//StockTrader Web App in that we do not talk 'directly' to the generated proxy
//for the service; rather we channel all calls through a single
//class that then talks to the service proxy. This will aid in more
//easily knowing where communication and proxy logic sits; and make changes to services
//you might want to interface with vs. littering proxy calls throughout the
//business tier itself.
TradeOrderServiceAsyncClient asyncclient = new TradeOrderServiceAsyncClient(Settings.orderMode);
asyncclient.processOrderASync(order);
}
else
{
processOrderSync(order, holding);
}
//Commit!
tx.Complete();
return order;
}
//If per chance you are doing step-through debugging through here and are getting a
// "TRANSACTION HAS ABORTED" exception and do not know why,
//it's quite likely you are hitting the 15-second timeout we set in
//ConfigurationSettings for the System.Transaction options so its just doing what we told it to.
//Simply adjust timeout higher, recompile if you need to.
catch
{
throw;
}
finally
{
dalOrder.Close();
}
}
}
//Repeat for ADO.NET transactions config option case.
case (StockTraderUtility.TRANSACTION_MODEL_ADONET_TRANSACTION):
{
dalOrder.Open(Settings.TRADEDB_SQL_CONN_STRING);
dalOrder.BeginADOTransaction();
try
{
//Business Step 1: create the order header.
order = createOrder(orderType, userID, holdingID, symbol, quantity, ref holding);
//Business Step 2: Determine which order processing mode to use,
//and either process order right away (sync); or interface with the
//async WCF Order Processing Service (cooler) via a service-to-service call.
if (Settings.orderMode != StockTraderUtility.ORDER_Sync)
{
//Fire up our async client; we follow the same model here as with the
//StockTrader Web App in that we do not talk 'directly' to the generated proxy
//for the service; rather we channel all calls through a single
//class that then talks to the service proxy. This will aid in more
//easily knowing where communication and proxy logic sits; and make changes to services
//you might want to interface with vs. littering proxy calls throughout the
//business tier itself.
TradeOrderServiceAsyncClient asyncclient = new TradeOrderServiceAsyncClient(Settings.orderMode);
asyncclient.processOrderASync(order);
dalOrder.CommitADOTransaction();
}
else
{
processOrderSync(order, holding);
}
dalOrder.CommitADOTransaction();
return order;
}
catch
{
dalOrder.RollBackTransaction();
throw;
}
finally
{
dalOrder.Close();
}
}
}
throw new Exception(Settings.ENABLE_GLOBAL_SYSTEM_DOT_TRANSACTIONS_CONFIGSTRING + ": " + StockTraderUtility.EXCEPTION_MESSAGE_INVALID_TXMMODEL_CONFIG + " Repository ConfigKey table.");
}
///
/// Business logic to create the order header.
/// The order header is always created synchronously by Trade; its the actual
/// processing of the order that can be done asynchrounously via the WCF service.
/// If, however, this service cannot communicate with the async order processor,
/// the order header is rolled back out of the database since we are wrapped in a tx here
/// either ADO.NET tx or System.TransactionScope as noted above, based on user setting.
///
private OrderDataModel createOrder(string orderType, string userID, int holdingID, string symbol, double quantity, ref HoldingDataModel holding)
{
OrderDataModel order;
switch (orderType)
{
case StockTraderUtility.ORDER_TYPE_SELL:
{
holding = dalOrder.getHolding(holdingID);
if (holding == null)
throw new Exception(StockTraderUtility.EXCEPTION_MESSAGE_INVALID_HOLDING_NOT_FOUND);
order = dalOrder.createOrder(userID, holding.quoteID, StockTraderUtility.ORDER_TYPE_SELL, holding.quantity, holdingID);
break;
}
//StockTrader, unlike WebSphere Trade 6.1, allows users to sell part
//of a holding, not required to dump all shares at once. This business logic
//on the processing side of the pipe is more tricky. Have to check for
//conditions like another order coming in and the shares do not exist anymore
//in the holding, etc. This is not done here--it is done in the ProcessOrder class.
//Here we are merely creatingt he order header as with all orders--note its just the *quantity* variable
//that varies with SELL_ENHANCED case vs. SELL. SELL always uses the total
//number of shares in the holding to sell ala IBM Trade 6.1; here we have obtained from the
//requesting service (for example, the StockTrader Web App), how many shares
//the user actually wants to sell.
case StockTraderUtility.ORDER_TYPE_SELL_ENHANCED:
{
holding = dalOrder.getHolding(holdingID);
if (holding == null)
throw new Exception(StockTraderUtility.EXCEPTION_MESSAGE_INVALID_HOLDING_NOT_FOUND);
//If user requests to sell more shares than in holding, we will just invoke the core
//sell operation which liquidates/sells the entire holding. Seems logical--they will
//get proper notification based on the client order alert in the Web app how many
//were actually sold.
if (quantity > holding.quantity)
{
goto case StockTraderUtility.ORDER_TYPE_SELL;
}
else
{
order = dalOrder.createOrder(userID, holding.quoteID, StockTraderUtility.ORDER_TYPE_SELL, quantity, holdingID);
break;
}
}
case StockTraderUtility.ORDER_TYPE_BUY:
{
//Buys are easier business case! Especially when on unlimited margin accounts :-).
order = dalOrder.createOrder(userID, symbol, StockTraderUtility.ORDER_TYPE_BUY, quantity, -1);
break;
}
default:
throw new Exception(StockTraderUtility.EXCEPTION_MESSAGE_BADORDERTYPE);
}
return order;
}
///
/// Business logic to synchrounously process the order within BSL layer, used with OrderMode="Sync."
///
/// Order information model class with header info.
/// Holding model class with holding to sell if applicable.
public void processOrderSync(OrderDataModel order, HoldingDataModel holding)
{
try
{
decimal total = 0;
int holdingid = -1;
QuoteDataModel quote = dalOrder.getQuoteForUpdate(order.symbol);
//Get the latest trading price--this is the money going into (or out of) the users account.
order.price = quote.price;
//Calculate order total, and create/update the Holding. Whole holding
//sells delete the holding, partials sells update the holding with new amount
//(and potentially the order too), and buys create a new holding.
if (order.orderType == StockTraderUtility.ORDER_TYPE_BUY)
{
holdingid = dalOrder.createHolding(order);
total = Convert.ToDecimal(order.quantity) * order.price + order.orderFee;
}
else
if (order.orderType == StockTraderUtility.ORDER_TYPE_SELL)
{
holdingid = sellHolding(order, holding);
total = -1 * Convert.ToDecimal(order.quantity) * order.price + order.orderFee;
}
//Debit/Credit User Account. Note, if we did not want to allow unlimited margin
//trading, we would change the ordering a bit and add the biz logic here to make
//sure the user has enough money to actually buy the shares they asked for!
//Now Update Account Balance.
dalOrder.updateAccountBalance(order.accountID, total);
//Update the stock trading volume and price in the quote table
dalOrder.updateStockPriceVolume(order.quantity, quote);
//Now perform our ACID tx test, if requested based on order type and acid stock symbols
if (order.symbol.Equals(StockTraderUtility.ACID_TEST_BUY) && order.orderType == StockTraderUtility.ORDER_TYPE_BUY)
throw new Exception(StockTraderUtility.EXCEPTION_MESSAGE_ACID_BUY);
else
if (order.symbol.Equals(StockTraderUtility.ACID_TEST_SELL) && order.orderType == StockTraderUtility.ORDER_TYPE_SELL)
throw new Exception(StockTraderUtility.EXCEPTION_MESSAGE_ACID_SELL);
//Finally, close the order
order.orderStatus = StockTraderUtility.ORDER_STATUS_CLOSED;
order.completionDate = DateTime.Now;
order.holdingID = holdingid;
dalOrder.closeOrder(order);
//Done!
return;
}
catch
{
throw;
}
}
///
/// Sell the holding.
///
///
///
///
int sellHolding(OrderDataModel order, HoldingDataModel holding)
{
int holdingid = holding.holdingID;
//There are three distinct business cases here, each needs different treatment:
// a) Quantity requested is less than total shares in the holding -- update holding.
// b) Quantity requested is equal to total shares in the holding -- delete holding.
// c) Quantity requested is greater than total shares in the holding -- delete holding, update order table.
if (order.quantity < holding.quantity)
{
dalOrder.updateHolding(holdingid, order.quantity);
}
else
if (holding.quantity == order.quantity)
{
dalOrder.deleteHolding(holdingid);
}
else
//We now need to back-update the order record quantity to reflect
//fact not all shares originally requested were sold since the holding
//had less shares in it, perhaps due to other orders
//placed against that holding that completed before this one. So we will
//sell the remaining shares, but need to update the final order to reflect this.
if (order.quantity > holding.quantity)
{
dalOrder.deleteHolding(holdingid);
order.quantity = holding.quantity;
dalOrder.updateOrder(order);
}
return holdingid;
}
}
}