//
// 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 WCF client to the BSL.
//======================================================================================================
using System;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Web;
using Trade.StockTraderWebApplicationSettings;
using Trade.StockTraderWebApplicationModelClasses;
using Trade.BusinessServiceImplementation;
using Trade.BusinessServiceDataContract;
using Trade.BusinessServiceContract;
using Trade.Utility;
namespace Trade.StockTraderWebApplicationServiceClient
{
///
/// This is the business services client class that is called from StockTrader Web pages. It will actually
/// call into our WCF BusinessServiceClient class, if configured for remote activation.
///
public class BSLClient
{
ITradeServices BSL;
///
/// Depending on AccessMode, this constructor returns an instance of our WCF client class, or
/// a direct instance of our TradeServices implementation class.
///
public BSLClient()
{
switch (Settings.interfaceMode)
{
//In-process activation---instantiate BSL directly, no need for client interface.
case StockTraderUtility.ACCESS_Direct:
{
BSL = new TradeService();
break;
}
//Remote activation.
//Note the same WCF client is used regardless of the
//specific webservice implementation platform. For StockTrader, our client
//interface for SOA modes is always the same: BusinessServiceClient.
//We differentiate WebSphere only becuase StockTrader has some additional UI and
//backend service functionality not provided by J2EE/Trade 6.1, and we need to detect
//in just a couple of places in the Web app so we do not make method calls to an
//implementation that has not implemented those methods. But as you see here, the
//WCF client is always the same regardless.
default:
{
BSL = new BusinessServiceClient();
break;
}
}
}
///
/// Logs user in/authenticates against StockTrader database.
///
/// User id to authenticate.
/// Password for authentication
public AccountDataUI login(string userid, string password)
{
try
{
AccountDataModel customer = BSL.login(userid, password);
if (customer == null)
return null;
return new AccountDataUI((int)customer.accountID, customer.profileID, customer.creationDate, customer.openBalance, customer.logoutCount, customer.balance, customer.lastLogin, customer.loginCount);
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.login Error: " + e.ToString(),EventLogEntryType.Error,true,Settings.EVENT_LOG);
throw;
}
}
///
/// Gets recent orders for a user. Transforms data from DataContract to model UI class for HTML display.
///
/// User id to retrieve data for.
public TotalOrdersUI getOrders(string userID)
{
try
{
List orders = BSL.getOrders(userID);
List ordersUI = new List();
decimal subtotalSell = 0;
decimal subtotalBuy = 0;
decimal subtotalTxnFee = 0;
for (int i = 0; i < orders.Count; i++)
{
subtotalTxnFee += orders[i].orderFee;
if (orders[i].orderType == StockTraderUtility.ORDER_TYPE_SELL)
{
subtotalSell += orders[i].price * (decimal)orders[i].quantity - orders[i].orderFee;
}
else
{
subtotalBuy += orders[i].price * (decimal) orders[i].quantity + orders[i].orderFee;
}
ordersUI.Add((convertOrderToUI(orders[i])));
}
TotalOrdersUI totalOrders = new TotalOrdersUI(ordersUI, subtotalSell, subtotalBuy, subtotalTxnFee);
return totalOrders;
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.getOrders Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
throw;
}
}
///
/// 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 TotalOrdersUI getTopOrders(string userID)
{
try
{
List orders = BSL.getTopOrders(userID);
List ordersUI = new List();
decimal subtotalSell = 0;
decimal subtotalBuy = 0;
decimal subtotalTxnFee = 0;
for (int i = 0; i < orders.Count; i++)
{
subtotalTxnFee += orders[i].orderFee;
if (orders[i].orderType == StockTraderUtility.ORDER_TYPE_SELL)
{
subtotalSell += orders[i].price * (decimal)orders[i].quantity - orders[i].orderFee;
}
else
{
subtotalBuy += orders[i].price * (decimal)orders[i].quantity + orders[i].orderFee;
}
ordersUI.Add((convertOrderToUI(orders[i])));
}
TotalOrdersUI totalOrders = new TotalOrdersUI(ordersUI, subtotalSell, subtotalBuy, subtotalTxnFee);
return totalOrders;
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.getTopOrders Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
throw;
}
}
///
/// Gets account data for a user. Transforms data from DataContract to model UI class for HTML display.
///
/// User id to retrieve data for.
public AccountDataUI getAccountData(string userID)
{
try
{
AccountDataModel customer = BSL.getAccountData(userID);
return new AccountDataUI((int)customer.accountID, customer.profileID, customer.creationDate, customer.openBalance, customer.logoutCount, customer.balance, customer.lastLogin, customer.loginCount);
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.getAccountData Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
throw;
}
}
///
/// 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 AccountProfileDataUI getAccountProfileData(string userID)
{
try
{
AccountProfileDataModel customerprofile = BSL.getAccountProfileData(userID);
return new AccountProfileDataUI(userID, customerprofile.password, customerprofile.fullName, customerprofile.address, customerprofile.email, customerprofile.creditCard);
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.getAccountProfileData Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
throw;
}
}
///
/// Updates account profile data for a user.
///
/// Profile data model class with updated info.
public AccountProfileDataUI updateAccountProfile(AccountProfileDataUI customerprofile)
{
try
{
AccountProfileDataModel serviceLayerCustomerProfile = convertCustProfileFromUI(customerprofile);
serviceLayerCustomerProfile = BSL.updateAccountProfile(serviceLayerCustomerProfile);
return new AccountProfileDataUI(serviceLayerCustomerProfile.userID, serviceLayerCustomerProfile.password, serviceLayerCustomerProfile.fullName, serviceLayerCustomerProfile.address, serviceLayerCustomerProfile.email, serviceLayerCustomerProfile.creditCard);
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.updateAccountProfile Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
throw;
}
}
///
/// Gets holding data for a user. Transforms data from DataContract to model UI class for HTML display.
///
/// User id to retrieve data for.
public Trade.StockTraderWebApplicationModelClasses.TotalHoldingsUI getHoldings(string userID)
{
try
{
List holdings = BSL.getHoldings(userID);
List holdingsUI = new List();
decimal marketValue = 0;
decimal gain = 0;
decimal basis = 0;
for (int i = 0; i < holdings.Count; i++)
{
//Note that here, we are constrained for interop purposes
//with WebSphere to individually query for each stock price as a separate call for
//each holding. The better approach is to get this data in a join query with the holding;
//reducing web service round trips and database round trips. This is presumably becuase
//of issues using entity beans with join statements and breaking the 1:1 object to table
//relationship that entity beans are designed for. This is an example (there are others)
//where entity beans/CMP and EJBQL can potentially reduce flexibility to "do the right" thing
//with a relational database by not supporting ANSI SQL. At any rate, we follow Trade 6.1 here, and do extra queries and
//Web service calls when they are not necessary. See the commented query in customer.cs
//for a better way of handling, especially considering every time a holding comes back to
//the UI, it also displays the current quote price for that holding.
//A quite unnecessary Web service call for every holding:
QuoteDataModel quote = BSL.getQuote(holdings[i].quoteID);
HoldingDataUI holdingitem = new HoldingDataUI(holdings[i].holdingID, holdings[i].quantity, holdings[i].purchasePrice, holdings[i].purchaseDate.ToString(), holdings[i].quoteID, quote.price);
holdingitem.convertNumericsForDisplay(false);
holdingsUI.Add(holdingitem);
decimal _marketValue = (decimal)holdings[i].quantity * quote.price;
decimal _basis = (decimal)holdings[i].quantity * (decimal)holdings[i].purchasePrice;
gain += _marketValue - _basis;
marketValue += _marketValue;
basis += _basis;
}
TotalHoldingsUI totalHoldings = new TotalHoldingsUI(holdingsUI, marketValue, basis, gain);
return totalHoldings;
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.getHoldings Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
throw;
}
}
///
/// This routine allows us to take an unsorted list (or list sorted by HoldingID) of holdings,
/// such as that returned by the WebSphere Trade 6.1 service, and return a sorted list of
/// holdings by stock symbol. At the same time, we produce subtotal lines for each unique stock.
/// This is used in a new page we added for StockTrader that Trade 6.1 does not have:
/// (PortfolioBySymbol.aspx). Yet, it will work with the existing WebSphere backend service,
/// since we do the sort here on the UI tier. We do it this way becuase WebSphere Trade 6.1
/// will always return an unsorted list of stock holdings since it does not implement a web service
/// method to return a list sorted by quoteID. Without this limiting factor, a better and more
/// performant way of doing this would be to implement a business services method and corresponding DAL method
/// to execute a query that returned pre-sorted values by stock symbol (as opposed to unsorted,
/// as the EJB/Trade 6.1 getHoldings operation does; or sorted by HoldingID as our getHoldings operation
/// does. This would avoid the need to do a sort on the UI tier at all. The extra load this
/// would place on the database would be negligable, given a typically small number of rows returned.
/// At any rate, the sort is implemented here by taking advantage of a custom comparer in our
/// HoldingsUI class on the quoteID property (stock symbol), and .NET's ability to sort
/// generic typed lists.
/// Hence, we can display even WebSphere-returned data that is non sorted in our extra
/// PortfolioBySymbol page, adding the subtotal lines for each unique stock as well.
/// We resisted adding a second method to the BSL and DAL to return a different sort order (quoteID), simply to
/// be consistent across the InProcess and Web Service modes of operation and for true benchmark comparison
/// purposes between the InProcess and web service modes. Note this logic is never called in
/// published benchmark data, given the fact WebSphere Trade 6.1 does not implement this
/// functionality, although even with the extra sort it performs very well.
///
/// User id to retrieve data for.
public Trade.StockTraderWebApplicationModelClasses.TotalHoldingsUI getHoldingsBySymbolSubTotaled(string userID)
{
try
{
//get the list of holdings from the BSL
List holdings = BSL.getHoldings(userID);
List holdingsUI = new List();
decimal marketValue = 0;
decimal gain = 0;
decimal basis = 0;
//create our HoldingsUI class to pass back to the ASPX page.
for (int i = 0; i < holdings.Count; i++)
{
//A quite unnecessary Web service call for every holding--again for Trade 6.1 compatibility:
QuoteDataModel quote = BSL.getQuote(holdings[i].quoteID);
HoldingDataUI holdingitem = new HoldingDataUI(holdings[i].holdingID, holdings[i].quantity, holdings[i].purchasePrice, holdings[i].purchaseDate.ToString(), holdings[i].quoteID, quote.price);
holdingsUI.Add(holdingitem);
decimal _marketValue = (decimal)holdings[i].quantity * quote.price;
decimal _basis = (decimal)holdings[i].quantity * holdings[i].purchasePrice;
gain += _marketValue - _basis;
marketValue += _marketValue;
basis += _basis;
}
//Call our implemented comparer class: see Trade.StockTraderWebApplicationModelClasses.HoldingDataUI.cs for the compararer
//class source code.
HoldingDataUI.HoldingDataUIComparer comparer = new HoldingDataUI.HoldingDataUIComparer();
comparer.ComparisonMethod = HoldingDataUI.HoldingDataUIComparer.ComparisonType.quoteID;
//Do the sort! Sort method is built into the C# Generic List functionality; the comparer
//calls our delegate method to compare by quoteID, so just one line of code here!
holdingsUI.Sort(comparer);
//Our list is now sorted, proceed with building in the subtotal lines.
htmlRowBuilder rowBuilder = new htmlRowBuilder();
int uniqueStockCount = rowBuilder.buildPortfolioBySymbol(holdingsUI);
TotalHoldingsUI totalHoldings = new TotalHoldingsUI(holdingsUI, marketValue, basis, gain, uniqueStockCount, holdingsUI.Count - uniqueStockCount);
return totalHoldings;
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.getHoldingsBySymbolSubTotaled Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
throw;
}
}
///
/// 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 HoldingDataUI getHolding(string userID, int holdingid)
{
try
{
HoldingDataModel holding = BSL.getHolding(userID,holdingid);
QuoteDataModel quote = BSL.getQuote(holding.quoteID);
HoldingDataUI holdingitem = new HoldingDataUI(holding.holdingID, holding.quantity, holding.purchasePrice, holding.purchaseDate, holding.quoteID, quote.price);
return holdingitem;
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.getHolding Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
throw;
}
}
///
/// Logs a user out--updates logout count.
///
/// User id to logout.
public void logout(string userID)
{
try
{
BSL.logout(userID);
return;
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.logout Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
}
}
///
/// 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. Might as well make it lots of $!
public AccountDataUI register(string userID, string password, string fullname, string address, string email, string creditcard, decimal openBalance)
{
try
{
AccountDataModel customer = BSL.register(userID, password, fullname, address, email, creditcard, openBalance);
return new AccountDataUI(customer.accountID, customer.profileID, customer.creationDate, customer.openBalance, customer.logoutCount, customer.balance, customer.lastLogin, customer.loginCount);
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.register Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
throw;
}
}
///
/// 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)
{
try
{
List ordersUI = new List();
List orders = BSL.getClosedOrders(userID);
for (int i = 0; i < orders.Count; i++)
{
ordersUI.Add((convertOrderToUI(orders[i])));
}
return ordersUI;
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.getClosedOrders Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
throw;
}
}
///
/// Performs a stock buy operation.
///
/// User id to create/submit order for.
/// Stock symbol to buy.
/// Shares to buy
public OrderDataUI buy(string userID, string symbol, double quantity)
{
try
{
return convertOrderToUI(BSL.buy(userID, symbol, quantity, 0));
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.buy Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
throw;
}
}
///
/// Performs a holding sell operation.
///
/// User id to create/submit order for.
/// Holding id to sell off.
/// Shares to sell.
public OrderDataUI sell(string userID, int holdingID, double quantity)
{
try
{
if (quantity==0)
return convertOrderToUI(BSL.sell(userID, holdingID, 0));
else
return convertOrderToUI(BSL.sellEnhanced(userID, holdingID, quantity));
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.sell Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
throw;
}
}
///
/// Gets a list of stock quotes based on symbols.
///
/// Symbols to get data for.
public List getQuotes(string symbols)
{
try
{
string[] quotes = symbols.Split(new char[] { ' ', ',', ';' });
//Note: would be much more efficient to add and call a method getQuotes(string quoteString) to the
// bsl class (TradeService), and do the parsing after the call to the bsl
// since this would reduce round trips for multiple quotes. However,
// WebSphere Trade 6.1 does not implement such a method, so for interop,
// neither does .NET StockTrader. The performance cost is not so large here in the InProcess mode,
// but it is in the web service modes where each call is remote,
// and serialization/deserialization is happening with each QuoteDataModel object requested.
List quoteList = new List();
foreach (string quote in quotes)
{
string stringquotetrim = quote.Trim();
if (!stringquotetrim.Equals(""))
{
QuoteDataUI quoteData = convertQuoteToUI(BSL.getQuote(stringquotetrim));
if (quoteData != null)
{
quoteList.Add(quoteData);
}
}
}
return quoteList;
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.getQuotes Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
throw;
}
}
///
/// Gets a single quote based on symbol.
///
/// Symbol to get data for.
public QuoteDataUI getQuote(string symbol)
{
try
{
return convertQuoteToUI(BSL.getQuote(symbol));
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.getQuote Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
throw;
}
}
///
/// 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 Trade.StockTraderWebApplicationModelClasses.MarketSummaryDataUI getMarketSummary()
{
try
{
return convertMarketSummaryDataToUI(BSL.getMarketSummary());
}
catch (Exception e)
{
StockTraderUtility.writeErrorConsoleMessage("StockTraderWebApplicationServiceClient.getMarketSummary Error: " + e.ToString(), EventLogEntryType.Error, true, Settings.EVENT_LOG);
throw;
}
}
///
/// Converts from service data contract model class to a UI Model class for quick HTML display in ASPX pages.
///
private QuoteDataUI convertQuoteToUI(QuoteDataModel quote)
{
if (quote != null)
return new QuoteDataUI(quote.symbol, quote.companyName, quote.volume, quote.price, quote.open, quote.low, quote.high, quote.change);
else return null;
}
///
/// Converts from service data contract model class to a UI Model class for quick HTML display in ASPX pages.
///
private Trade.StockTraderWebApplicationModelClasses.MarketSummaryDataUI convertMarketSummaryDataToUI(MarketSummaryDataModelWS data)
{
List quoteGainers = new List();
List quoteLosers = new List();
for (int i = 0; i < data.topGainers.Count; i++)
{
QuoteDataModel quote = (QuoteDataModel)data.topGainers[i];
quoteGainers.Add((convertQuoteToUI(quote)));
}
for (int i = 0; i < data.topLosers.Count; i++)
{
QuoteDataModel quote = (QuoteDataModel)data.topLosers[i];
quoteLosers.Add((convertQuoteToUI(quote)));
}
return new MarketSummaryDataUI(data.TSIA, data.openTSIA, data.volume, quoteGainers, quoteLosers, data.summaryDate);
}
///
/// Converts from service data contract model class to a UI Model class for quick HTML display in ASPX pages.
///
private OrderDataUI convertOrderToUI(OrderDataModel order)
{
string completionDate;
if (order != null)
{
//MinValue used to indicate "null" data in the DB; equates to a pending order.
if (order.completionDate == DateTime.MinValue)
completionDate = "Pending";
else
completionDate = order.completionDate.ToString();
return new OrderDataUI(order.orderID, order.orderType, order.orderStatus, order.openDate, completionDate, order.quantity, order.price, order.orderFee, order.symbol);
}
else
return null;
}
///
/// Converts from service data contract model class to a UI Model class for quick HTML display in ASPX pages.
///
private AccountDataModel convertAccountDataFromUI(AccountDataUI customer)
{
AccountDataModel serviceLayerCustomer = new AccountDataModel();
serviceLayerCustomer.accountID = (int)customer.accountID;
serviceLayerCustomer.balance = customer.balance;
serviceLayerCustomer.creationDate = customer.creationDate;
serviceLayerCustomer.lastLogin = customer.lastLogin;
serviceLayerCustomer.logoutCount = customer.logoutCount;
serviceLayerCustomer.openBalance = customer.openBalance;
serviceLayerCustomer.profileID = customer.profileID;
return serviceLayerCustomer;
}
///
/// Converts from service data contract model class to a UI Model class for quick HTML display in ASPX pages.
///
private AccountProfileDataModel convertCustProfileFromUI(AccountProfileDataUI customerprofile)
{
AccountProfileDataModel serviceLayerCustomerProfile = new AccountProfileDataModel();
serviceLayerCustomerProfile.password = customerprofile.password;
serviceLayerCustomerProfile.address = customerprofile.address;
serviceLayerCustomerProfile.creditCard = customerprofile.creditCard;
serviceLayerCustomerProfile.email = customerprofile.email;
serviceLayerCustomerProfile.fullName = customerprofile.fullName;
serviceLayerCustomerProfile.userID = customerprofile.userID;
return serviceLayerCustomerProfile;
}
}
}