/** * 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. */ package org.apache.openjpa.integration.daytrader; import java.util.Collection; import java.util.Iterator; import java.io.IOException; /** * TradeScenarioServlet emulates a population of web users by generating a specific Trade operation * for a randomly chosen user on each access to the URL. Test this servlet by clicking Trade Scenario * and hit "Reload" on your browser to step through a Trade Scenario. To benchmark using this URL aim * your favorite web load generator (such as AKStress) at the Trade Scenario URL and fire away. */ public class TradeScenario { private TradeAction tAction = null; public TradeScenario(TradeAction tAction) { this.tAction = tAction; } /** * Perform the following 15 tasks for the given userID: * login, home, account, update, home, portfolio, sell, buy, home, portfolio, sell, buy, home, account, logout * * @param userID */ public boolean performUserTasks(String userID) { StringBuilder sb = new StringBuilder(16384); boolean brc = false; if (TradeConfig.log.isTraceEnabled()) { TradeConfig.log.trace("TradeScenario.performUserTasks(" + userID + ")"); } try { // login log(sb, performTask("l", userID)); // home page log(sb, performTask("h", userID)); // account info and orders log(sb, performTask("a", userID)); // update account info log(sb, performTask("u", userID)); // home page log(sb, performTask("h", userID)); // portfolio holdings log(sb, performTask("p", userID)); // sell log(sb, performTask("s", userID)); // buy log(sb, performTask("b", userID)); // home page log(sb, performTask("h", userID)); // portfolio holdings log(sb, performTask("p", userID)); // sell log(sb, performTask("s", userID)); // buy log(sb, performTask("b", userID)); // home page log(sb, performTask("h", userID)); // account info and orders log(sb, performTask("a", userID)); // logout log(sb, performTask("o", userID)); brc = true; if (TradeConfig.log.isTraceEnabled()) { TradeConfig.log.trace(sb.toString()); } } catch (Exception e) { TradeConfig.log.error("TradeScenario.performUserTasks(" + userID + ") failed", e); } return brc; } /** * Main service method for TradeScenarioServlet * * @param request Object that encapsulates the request to the servlet * @param response Object that encapsulates the response from the servlet */ public String performTask(String scenarioAction, String userID) throws IOException { StringBuilder sb = new StringBuilder(256); String results = ""; // Scenario generator for Trade2 char action = ' '; // String to create full dispatch path to TradeAppServlet w/ request Parameters String dispPath = null; // Dispatch Path to TradeAppServlet if ((scenarioAction != null) && (scenarioAction.length() >= 1)) { action = scenarioAction.charAt(0); if (action == 'n') { //null; try { log(sb, "TradeScenario.performTask() scenarioAction=" + scenarioAction + ", userID=" + userID); } catch (Exception e) { log(sb, "ERROR - TradeScenario.performTask() - Exception ", e); } finally { return sb.toString(); } } //end of action=='n' } if (userID == null || userID.trim().length() == 0) { // These operations require the user to be logged in. Verify the user and if not logged in // change the operation to a login userID = null; action = 'l'; TradeConfig.incrementScenarioCount(); } else if (action == ' ') { //action is not specified perform a random operation according to current mix // Tell getScenarioAction if we are an original user or a registered user // -- sellDeficits should only be compensated for with original users. action = TradeConfig.getScenarioAction( userID.startsWith(TradeConfig.newUserPrefix)); } switch (action) { case 'q' : //quote tAction.doQuotes(sb, userID, TradeConfig.rndSymbols()); break; case 'a' : //account tAction.doAccount(sb, userID, results); break; case 'u' : //update account profile String fullName = "rnd" + System.currentTimeMillis(); String address = "rndAddress"; String password = "xxx"; String email = "rndEmail"; String creditcard = "rndCC"; tAction.doAccountUpdate(sb, userID, password, password, fullName, address, creditcard, email); break; case 'h' : //home tAction.doHome(sb, userID, results); break; case 'l' : //login userID = TradeConfig.getUserID(); String password2 = "xxx"; boolean brc = tAction.doLogin(sb, userID, password2); // login is successful if the userID is written to the HTTP session if (!brc) { log(sb, "TradeScenario login failed. Reset DB between runs."); } break; case 'o' : //logout tAction.doLogout(sb, userID); break; case 'p' : //portfolio tAction.doPortfolio(sb, userID, results); break; case 'r' : //register //Logout the current user to become a new user // see note in TradeServletAction tAction.doLogout(sb, userID); userID = TradeConfig.rndNewUserID(); String passwd = "yyy"; fullName = TradeConfig.rndFullName(); creditcard = TradeConfig.rndCreditCard(); String money = TradeConfig.rndBalance(); email = TradeConfig.rndEmail(userID); String smail = TradeConfig.rndAddress(); tAction.doRegister(sb, userID, passwd, passwd, fullName, creditcard, money, email, smail); break; case 's' : //sell Collection<HoldingDataBean> holdings = tAction.getHoldings(userID); int numHoldings = holdings.size(); if (numHoldings > 0) { //sell first available security out of holding Iterator it = holdings.iterator(); boolean foundHoldingToSell = false; while (it.hasNext()) { HoldingDataBean holdingData = (HoldingDataBean) it.next(); if ( !(holdingData.getPurchaseDate().equals(new java.util.Date(0))) ) { Integer holdingID = holdingData.getHoldingID(); tAction.doSell(sb, userID, holdingID); foundHoldingToSell = true; break; } } if (foundHoldingToSell) break; tAction.log.warn("TradeScenario: No holdings sold for userID=" + userID + ", holdings=" + numHoldings); } else { tAction.log.warn("TradeScenario: No holdings to sell for userID=" + userID); } // At this point: A TradeScenario Sell was requested with No Stocks in Portfolio // This can happen when a new registered user happens to request a sell before a buy // In this case, fall through and perform a buy instead tAction.log.warn("TradeScenario: No holdings sold - switching to buy -- userID=" + userID); /* Trade 2.037: Added sell_deficit counter to maintain correct buy/sell mix. * When a users portfolio is reduced to 0 holdings, a buy is requested instead of a sell. * This throws off the buy/sell mix by 1. This results in unwanted holding table growth * To fix we increment a sell deficit counter to maintain the correct ratio in getScenarioAction * The 'z' action from getScenario denotes that this is a sell action that was switched from a buy * to reduce a sellDeficit */ if (userID.startsWith(TradeConfig.newUserPrefix) == false) { TradeConfig.incrementSellDeficit(); } case 'b' : //buy String symbol = TradeConfig.rndSymbol(); String amount = TradeConfig.rndQuantity() + ""; tAction.doQuotes(sb, userID, symbol); tAction.doBuy(sb, userID, symbol, amount); break; } //end of switch statement log(sb, "Results", results); return sb.toString(); } private void log(StringBuilder sb, String msg) { if (tAction.log != null && tAction.log.isTraceEnabled()) { sb.append(msg); sb.append(System.getProperty("line.separator")); } } private void log(StringBuilder sb, String msg, Object obj) { if (tAction.log != null && tAction.log.isTraceEnabled()) { sb.append(msg); sb.append(" = "); sb.append(obj); sb.append(System.getProperty("line.separator")); } } }