First version of OpenTrader

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@988644 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Pinaki Poddar 2010-08-24 18:11:25 +00:00
parent 319184d64e
commit e0882d7cc0
45 changed files with 5858 additions and 0 deletions

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<module rename-to='opentrader'>
<!-- Inherit the core Web Toolkit stuff. -->
<inherits name='com.google.gwt.user.User'/>
<inherits name="com.google.gwt.resources.Resources" />
<!-- Inherit the default GWT style sheet. You can change -->
<!-- the theme of your GWT application by uncommenting -->
<!-- any one of the following lines. -->
<inherits name='com.google.gwt.user.theme.standard.Standard'/>
<inherits name='org.cobogw.gwt.user.User' />
<!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
<!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/> -->
<!-- Other module inherits -->
<!-- Specify the app entry point class. -->
<entry-point class='org.apache.openjpa.trader.client.OpenTrader'/>
<!-- Specify the paths for translatable code -->
<source path='client'/>
<source path='domain'/>
<set-property name="user.agent" value="gecko1_8"/>
</module>

View File

@ -0,0 +1,97 @@
/*
* 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.trader.client;
import com.google.gwt.i18n.client.NumberFormat;
import com.google.gwt.user.client.ui.HTML;
/**
* A set of static utilities to create decorated HTML labels for price and price changes.
*
* @author Pinaki Poddar
*
*/
public class FormatUtil {
public static final NumberFormat priceFormat = NumberFormat.getFormat("#,##0.00");
public static final NumberFormat changeFormat = NumberFormat.getFormat("+#,##0.00;-#,##0.00");
public static final NumberFormat volumeFormat = NumberFormat.getFormat("#,##0");
/**
* Creates a HTML for the formatted price without any style.
*/
public static HTML formatPrice(double price) {
return formatPrice(price, false);
}
/**
* Creates a HTML label for the formatted price with a style based on the signum of the price.
* The positive and negative values are formatted with CSS styles <code>positive</code> and
* <code>negative</code> respectively.
*/
public static HTML formatPrice(double price, boolean style) {
HTML label = new HTML();
label.setText(priceFormat.format(price));
if (style)
label.addStyleName(price >= 0 ? "positive" : "negative");
return label;
}
/**
* Creates a HTML label for the given integer.
*/
public static HTML formatVolume(int volume) {
HTML label = new HTML();
label.setText(volumeFormat.format(volume));
return label;
}
/**
* Creates a HTML label for the difference of two given numbers and applies style based
* on the sign of the difference. Optionally adds percentage change.
*
* @param p1 first value
* @param p2 second value
* @param pct if true, adds a percentage change
*/
public static HTML formatChange(double p1, double p2, boolean pct) {
String raw = changeFormat.format(p1-p2);
if (pct && p1 != 0) {
double delta = p1 - p2;
String pctraw = changeFormat.format(100*delta/p1)+"%";
raw += " (" + pctraw + ")";
}
String style = p1 >= p2 ? "positive" : "negative";
HTML html = new HTML();
html.setText(raw);
html.addStyleName(style);
return html;
}
/**
* Creates a HTML label for the given value and applies style based on the sign.
*/
public static HTML formatChange(double c) {
HTML html = new HTML();
String raw = changeFormat.format(c);
String style = c >= 0 ? "positive" : "negative";
html.setText(raw);
html.addStyleName(style);
return html;
}
}

View File

@ -0,0 +1,138 @@
/*
* 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.trader.client;
import java.util.List;
import org.apache.openjpa.trader.client.ui.MessageBox;
import org.apache.openjpa.trader.client.ui.ProgressMonitor;
import org.apache.openjpa.trader.domain.Stock;
import org.apache.openjpa.trader.domain.Trader;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.TextBox;
/**
* A dialog box for login a Trader. Once the trader's name is entered, this widget calls the server
* to start a session for the trader, gets all the tradable Stocks and passes that initialization data
* to the {@link OpenTrader main application} to {@link OpenTrader#init(Trader, List) initialize}.
*
* <br>
* CSS styles used
* <LI> login : for the main dialog box
* <LI> login-caption: for the caption
*
*
* @author Pinaki Poddar
*
*/
public class LoginDialog extends PopupPanel {
private Trader trader;
private final OpenTrader session;
public LoginDialog(final OpenTrader session) {
super(false, true);
setAnimationEnabled(true);
this.session = session;
final FlexTable table = new FlexTable();
final HTML header = new HTML("&nbsp;&nbsp;Welcome to OpenTrader&nbsp;&nbsp;");
final Label label = new Label("Please enter name:");
DOM.setStyleAttribute(label.getElement(), "textAlign", "right");
final TextBox traderName = new TextBox();
traderName.setText("OpenTrader");
final Button enter = new Button("Enter");
addStyleName("login");
table.addStyleName("login");
label.addStyleName("login");
header.addStyleName("login-caption");
table.setWidget(0, 0, header);
table.setWidget(2, 0, label);
table.setWidget(2, 1, traderName);
table.setWidget(4, 1, enter);
table.getFlexCellFormatter().setColSpan(0, 0, 2);
enter.setEnabled(true);
traderName.setFocus(true);
enter.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
if (traderName.getText().trim().length() == 0) {
MessageBox.alert("Trader's name must not be empty.");
return;
}
hide();
ProgressMonitor.showProgress("Connecting to OpenTrader Server...");
session.getService().login(traderName.getText(), new LoginCallback());
}
});
setWidget(table);
}
/**
* ---------------------------------------------------------------------------------
* Asynchronous RPC service callbacks
* ---------------------------------------------------------------------------------
*/
/**
* Logs in a [@link Trader} and then invokes another RPC service to get the list of tradable stocks.
* This pattern of calling one RPC from another addresses sequential execution of asynchronous
* callbacks. This pattern is necessary when the second RPC depends in some way to the result of
* the first RPC.
*
*/
class LoginCallback implements AsyncCallback<Trader> {
public void onFailure(Throwable caught) {
ProgressMonitor.stop();
session.handleError(caught);
}
public void onSuccess(Trader result) {
trader = result;
session.getService().getStocks(new InitializeStocks());
}
}
/**
* Initializes the tradable stocks followed by the main application.
*
*/
public class InitializeStocks implements AsyncCallback<List<Stock>> {
public void onFailure(Throwable caught) {
session.handleError(caught);
}
public void onSuccess(List<Stock> stocks) {
ProgressMonitor.stop();
session.init(trader, stocks);
}
}
}

View File

@ -0,0 +1,165 @@
/*
* 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.trader.client;
import java.util.List;
import org.apache.openjpa.trader.client.event.ServiceEvent;
import org.apache.openjpa.trader.client.event.ServiceEventHandler.UpdateStockHandler;
import org.apache.openjpa.trader.client.ui.GridCellRenderer;
import org.apache.openjpa.trader.client.ui.ScrollableTable;
import org.apache.openjpa.trader.domain.Stock;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
/**
* Displays the current Stock prices and updates periodically.
*
*
* @author Pinaki Poddar
*
*/
public class MarketDataPanel extends ScrollableTable<Stock> implements UpdateStockHandler {
private final OpenTrader session;
private Timer refreshTimer;
private static int refreshInterval = 60*1000;
public MarketDataPanel(final OpenTrader session, final int w, final int h) {
super("Market Prices (Updated every " + refreshInterval/1000 + "s)", w, h, true);
this.session = session;
session.registerHandler(ServiceEvent.StockUpdated.TYPE, this);
setColumnHeader(0, "Symbol", "25%");
setColumnHeader(1, "Price", "25%");
setColumnHeader(2, "Change", "50%");
// Stock Symbol
setRenderer(0, new GridCellRenderer<Stock>() {
public Widget render(Stock stock) {
return new Label(stock.getSymbol());
}
});
// Current Market Price
setRenderer(1, new GridCellRenderer<Stock>() {
public Widget render(Stock stock) {
return FormatUtil.formatPrice(stock.getMarketPrice());
}
});
// Percent Change since last update
setRenderer(2, new GridCellRenderer<Stock>() {
public Widget render(Stock stock) {
return FormatUtil.formatChange(stock.getMarketPrice(), stock.getLastPrice(), true);
}
});
}
/**
* Sets the interval to refresh the stock data from the server.
*
* @param interval period in milliseconds.
*/
public void setRefreshInterval(int interval) {
refreshInterval = interval;
setCaption("Market Prices (Updated every " + refreshInterval/1000 + "s)");
if (refreshTimer != null)
refreshTimer.scheduleRepeating(refreshInterval);
}
/**
* Gets the interval (in milliseconds) to refresh the stock data from the server.
*/
public int getRefreshInterval() {
return refreshInterval;
}
/**
* Starts a periodic update of the stocks from the server.
*/
public void startStockWatcher() {
if (refreshTimer != null)
return;
// Setup timer to refresh list automatically.
refreshTimer = new Timer() {
@Override
public void run() {
session.getService().getStocks(new UpdateStocks());
}
};
refreshTimer.run();
refreshTimer.scheduleRepeating(refreshInterval);
}
/**
* Starts periodic update of the stocks from the server.
*/
public void stopStockWatcher() {
if (refreshTimer == null)
return;
refreshTimer.cancel();
refreshTimer = null;
}
/**
* ---------------------------------------------------------------------------------
* Service Event Response Management
* ---------------------------------------------------------------------------------
*/
/**
* Updates the stock data.
*/
@Override
public void onStockUpdated(ServiceEvent.StockUpdated event) {
update(event.getPayload(), null);
}
/**
* ---------------------------------------------------------------------------------
* Asynchronous RPC service callbacks
* ---------------------------------------------------------------------------------
*/
/**
* Periodically update the stocks and notifies the listeners via the
* {@link OpenTrader#fireEvent(com.google.gwt.event.shared.GwtEvent) mediator}.
* In this case, one of the listeners is this widget itself. Still the
* {@link ServiceEvent.StockUpdated service event} is propagated via the
* mediator (so that others can listen as well).
*
*/
public class UpdateStocks implements AsyncCallback<List<Stock>> {
public void onFailure(Throwable caught) {
session.handleError(caught);
}
public void onSuccess(List<Stock> result) {
int n = result.size();
for (int i = 0; i < n; i++) {
session.fireEvent(new ServiceEvent.StockUpdated(result.get(i)));
}
}
}
}

View File

@ -0,0 +1,144 @@
/*
* 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.trader.client;
import java.util.List;
import org.apache.openjpa.trader.client.event.ServiceEvent;
import org.apache.openjpa.trader.domain.Ask;
import org.apache.openjpa.trader.domain.Match;
import org.apache.openjpa.trader.domain.Tradable;
import org.apache.openjpa.trader.domain.Trade;
import org.apache.openjpa.trader.domain.Trader;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.RadioButton;
/**
* A popup presents a list of matching Tradables and lets the user select one of them
* to commit a trade.
*
* @author Pinaki Poddar
*
*/
public class MatchWindow extends PopupPanel {
private final OpenTrader session;
public MatchWindow(final OpenTrader session, final Tradable tradable, final List<Match> matches) {
super(false, true);
this.session = session;
final boolean ask = (tradable instanceof Ask);
final RadioButton[] buttons = new RadioButton[matches.size()];
FlowPanel panel = new FlowPanel();
String txt = (matches.isEmpty() ? "No" : matches.size()) + " matching " + (ask ? "Bid" : "Ask")
+ " for " + toString(tradable) + "<br>";
HTML html = new HTML();
html.setHTML(txt);
html.addStyleName("table-caption");
panel.add(html);
if (!matches.isEmpty()) {
FlexTable table = new FlexTable();
for (int i = 0; i < matches.size(); i++) {
Match match = matches.get(i);
Tradable t2 = ask ? match.getBid() : match.getAsk();
Trader cpty = ask ? match.getBid().getBuyer() : match.getAsk().getSeller();
buttons[i] = new RadioButton("matches");
buttons[i].setValue(i == 0);
table.setWidget(i, 0, buttons[i]);
table.setWidget(i, 1, FormatUtil.formatPrice(t2.getPrice()));
table.setWidget(i, 2, FormatUtil.formatVolume(t2.getVolume()));
table.setText(i, 3, " by " + cpty.getName());
}
panel.add(table);
panel.add(new HTML("<p>"));
Button act = new Button(ask ? "Sell" : "Buy");
panel.add(act);
act.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
for (int i = 0; i < buttons.length; i++) {
if (buttons[i].getValue()) {
Match match = matches.get(i);
Tradable t = ask ? match.getAsk() : match.getBid();
session.getService().trade(match, new TradeCallback(t));
hide(true);
}
}
}
});
} else {
panel.add(new HTML("<p>"));
}
Button cancel = new Button("Cancel");
panel.add(cancel);
cancel.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
hide(true);
}
});
add(panel);
}
String toString(Tradable t) {
return "" + t.getVolume() + " of " + t.getStock().getSymbol() + " at price "
+ FormatUtil.priceFormat.format(t.getPrice());
}
/**
* ---------------------------------------------------------------------------------
* Asynchronous RPC service callbacks
* ---------------------------------------------------------------------------------
*/
/**
* Commits a Trade and notifies the listeners to {@link ServiceEvent.TradableRemoved
* remove} the {@link Tradable tradable} and newly {@link ServiceEvent.TradeCommitted committed}
* {@link Trade trade}.
* <br>
* This is an example of a callback that has a input state (the tradable entity). On completion
* of the asynchronous RPC, this function will notify the listeners with the newly commited trade
* which is the result of the callback invocation, as well as the input tradable entity.
*/
public class TradeCallback implements AsyncCallback<Trade> {
private final Tradable tradable;
public TradeCallback(Tradable m) {
tradable = m;
}
public void onFailure(Throwable caught) {
session.handleError(caught);
}
public void onSuccess(Trade trade) {
session.fireEvent(new ServiceEvent.TradableRemoved(tradable));
session.fireEvent(new ServiceEvent.TradeCommitted(trade));
}
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.trader.client;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.ImageResource;
/**
* A bundle of resources (a couple of tiny images for now). However, GWT framework
* seems to have generalized and added performance boost to handle heavy resources
* such as images to be loaded from the web server.
* <br>
* This sample application does not explore this feature of GWT, only other than
* applying a singleton pattern.
*
* @author Pinaki Poddar
*
*/
public interface OpenTradeImageBundle extends ClientBundle {
@Source("images/login.gif")
public ImageResource login();
@Source("images/logo.gif")
public ImageResource logo();
}

View File

@ -0,0 +1,311 @@
/*
* 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.trader.client;
import java.util.List;
import org.apache.openjpa.trader.client.event.ServiceEvent;
import org.apache.openjpa.trader.client.ui.ErrorDialog;
import org.apache.openjpa.trader.client.ui.ScrollableTable;
import org.apache.openjpa.trader.domain.Ask;
import org.apache.openjpa.trader.domain.Bid;
import org.apache.openjpa.trader.domain.Stock;
import org.apache.openjpa.trader.domain.Trade;
import org.apache.openjpa.trader.domain.Trader;
import org.apache.openjpa.trader.service.TradingService;
import org.cobogw.gwt.user.client.ui.RoundedPanel;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.GWT.UncaughtExceptionHandler;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
import com.google.gwt.event.shared.HandlerManager;
import com.google.gwt.event.shared.GwtEvent.Type;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootLayoutPanel;
import com.google.gwt.user.client.ui.Widget;
/**
* The GWT module for OpenTrader.
*
* <br><b>Initialization</b>:
* This module entry point acts as the Application Controller.
* It initializes the widget components and lays them out. As it is often the case, some of
* the widgets require initialization data.
* Hence, this entry point establishes connection to remote {@link TradingService} via
* {@link TradingServiceAdapterAsync asynchronous}, client-side, proxy stub. The GWT
* framework provides that stub when supplied with the original interface class.
* <br>
* <b>Operation</b>:
* Once initialized, the widgets operate relatively independently throughout the rest of the
* operations. Each widget provides a view and invokes server functions (asynchronously via
* RPC) to update the view.
* <br>
* <b>Event Management</b>:
* The widgets communicate to other widgets via this module entry point i.e.
* if the server callback results from a view action requires to update another view, then the request
* is relayed through this entry point instead of one view calling the other directly.
* The GWT framework does provide the core infrastructure for
* DOM and user event propagation. This entry point reuses the same infrastructure with
* the application-defined specialized {@link ServiceEvent <em>service events</em>} to represent
* the <em>business</em> functions such as a new Ask or Bid request is placed, or a Trade
* committed etc.
* <br>
* <b>Session State Management</b>: One of the core advantages of GWT framework (apart from ease
* of development and out-of-the-box cross-browser compatibility), is that GWT allows the client to
* maintain its own state, thereby opening up the possibility of state-less (or at least less
* stateful and hence more scalable) servers. This client maintains its session state in the
* {@link ScrollableTable#getModel() data models} of the core widgets. The only <em>common,
* shared</em> state held by this Application Controller is the logged-in {@link Trader}.
*
*
* @author Pinaki Poddar
*
*/
public class OpenTrader implements EntryPoint, UncaughtExceptionHandler {
// Event management
private HandlerManager eventBus;
// The main widget components
private MarketDataPanel stockPanel; // the market prices
private TradeOrderWindow orderPanel; // Creates a Ask/Bid request
private TradingWindow tradePanel; // Issues a trade order
private ScrollableTable<Trade> soldTradePanel; // displays committed trades
private ScrollableTable<Trade> boughtTradePanel; // displays committed trades
private ServerLogPanel serverPanel; // displays server logs
// Server-State variables as Session Identifier
private Trader trader;
// The handle to the remote service.
private TradingServiceAdapterAsync tradingService;
// Resource bundle for images etc.
public static final OpenTradeImageBundle bundle = GWT.create(OpenTradeImageBundle.class);
/**
* ------------------------------------------------------------------------
* The entry point for GWT module.
* ------------------------------------------------------------------------
*/
public void onModuleLoad() {
GWT.setUncaughtExceptionHandler(this);
eventBus = new HandlerManager(this);
new LoginDialog(this).center();
}
/**
* Gets the handle to the remote service. The service handle is the asynchronous interface
* but it is created by the GWT framework from the synchronous interface class literal.
*/
public TradingServiceAdapterAsync getService() {
if (tradingService == null) {
tradingService = GWT.create(TradingServiceAdapter.class);
}
return tradingService;
}
/**
* Gets the name of the trader as the name of this session.
*/
public String getName() {
return trader == null ? "" : trader.getName();
}
/**
* Gets the trader who is running this session.
*/
public Trader getTrader() {
return trader;
}
/**
* Gets all the traded stocks traded by the service.
* The stocks are maintained by the {@link ScrollableTable#getModel() data model} of
* the Market Data Panel widget.
*/
public List<Stock> getTradedStocks() {
return stockPanel.getModel();
}
/**
* Builds up the widgets once the login is complete i.e. the server has supplied
* the initialization data.
*
*/
void init(Trader trader, List<Stock> stocks) {
this.trader = trader;
Window.setTitle(trader.getName());
int W = Window.getClientWidth();
int H = 900;//Window.getClientHeight();
int headerHeight = 05*H/100;
int westWidth = 25*W/100; int westHeight = 80*H/100;
int centerWidth = 60*W/100; int centerHeight = 80*H/100;
int footerHeight = 02*H/100;
Unit unit = Unit.PX;
stockPanel = new MarketDataPanel(this, westWidth-10, 40*westHeight/100);
int N = stocks.size();
for (int i = 0; i < N; i++) {
stockPanel.insert(stocks.get(i));
}
soldTradePanel = new TradeHistoryPanel(this, Ask.class, westWidth-10, 20*westHeight/100);
boughtTradePanel = new TradeHistoryPanel(this, Bid.class, westWidth-10, 20*westHeight/100);
serverPanel = new ServerLogPanel(this, centerWidth, 40*centerHeight/100);
tradePanel = new TradingWindow(this, centerWidth, 40*centerHeight/100);
orderPanel = new TradeOrderWindow(this,centerWidth, 10*centerHeight/100);
FlowPanel west = new FlowPanel();
west.setSize((westWidth-10)+"px", westHeight+"px");
west.add(decorate(stockPanel));
west.add(new HTML("<p>"));
west.add(decorate(soldTradePanel));
west.add(new HTML("<p>"));
west.add(decorate(boughtTradePanel));
FlowPanel center = new FlowPanel();
center.setSize(centerWidth+"px", centerHeight+"px");
center.add(decorate(orderPanel));
center.add(new HTML("<p>"));
center.add(decorate(tradePanel));
center.add(new HTML("<p>"));
center.add(decorate(serverPanel));
DockLayoutPanel main = new DockLayoutPanel(unit);
main.addNorth(createHeader(), headerHeight);
main.addSouth(createFooter(), footerHeight);
main.addWest(west, westWidth);
main.add(center);
RootLayoutPanel.get().add(main);
main.animate(500);
setUpHelp();
stockPanel.startStockWatcher();
tradePanel.startTradableRefresher();
}
/**
* Decorates an widget by wrapping in a rounded panel (that seems to be cool thing nowadays).
*/
Widget decorate(Widget w) {
RoundedPanel rp = new RoundedPanel(w,RoundedPanel.ALL, 2);
rp.setBorderColor("#005B9A");
return rp;
}
/**
* Sets up a help page for each of the main widgets.
*
* @see ScrollableTable#addHelp(String)
*/
void setUpHelp() {
stockPanel.addHelp("help/MarketData.html");
tradePanel.addHelp("help/Trade.html");
orderPanel.addHelp("help/TradeOrder.html");
soldTradePanel.addHelp("help/CommittedTrade.html");
boughtTradePanel.addHelp("help/CommittedTrade.html");
serverPanel.addHelp("help/Logging.html");
}
/**
* Creates a header panel. Uses the image resources for a logo and a banner text.
*/
Widget createHeader() {
HorizontalPanel panel = new HorizontalPanel();
Image logo = new Image(bundle.logo());
HTML banner = new HTML("OpenTrader");
banner.setStylePrimaryName("header");
panel.add(logo);
panel.add(banner);
return panel;
}
/**
* Creates a footer panel.
*/
Widget createFooter() {
HorizontalPanel panel = new HorizontalPanel();
Label footer = new HTML("Built with OpenJPA/Slice and GWT");
footer.setStylePrimaryName("footer");
panel.add(footer);
return panel;
}
/**
* ---------------------------------------------------------------------------------
* Error Handling
* ---------------------------------------------------------------------------------
*/
/**
* Catches any uncaught exception and pops up a {@link ErrorDialog error dialog}.
*/
public void onUncaughtException(Throwable t) {
handleError(t);
}
/**
* Pops up a modal {@link ErrorDialog error dialog} with the given error.
*/
void handleError(Throwable t) {
t.printStackTrace();
ErrorDialog.showError(t);
}
/**
* ---------------------------------------------------------------------------------
* Service Event Handling
* ---------------------------------------------------------------------------------
*/
/**
* Registers a event with its handler. This mediator pattern facilitates communication
* between the component widgets without them being aware of each other.
* <br>
* GWT framework supports this patter out-of-the-box. This application reuses the
* framework for a set of {@link ServiceEvent service events}.
*/
public <H extends EventHandler> void registerHandler(Type<H> eventType, H handler) {
eventBus.addHandler(eventType, handler);
}
/**
* Fires a event to the registered handlers.
*
* @param event can be any GwtEvent but used here for specialized {@link ServiceEvent service events}.
*/
public void fireEvent(GwtEvent<? extends EventHandler> event) {
eventBus.fireEvent(event);
}
}

View File

@ -0,0 +1,159 @@
/*
* 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.trader.client;
import java.util.List;
import org.apache.openjpa.trader.client.event.ServiceEvent;
import org.apache.openjpa.trader.client.event.ServiceEventHandler.AddTradableHandler;
import org.apache.openjpa.trader.client.event.ServiceEventHandler.AddTradeHandler;
import org.apache.openjpa.trader.client.event.ServiceEventHandler.RemoveTradableHandler;
import org.apache.openjpa.trader.client.event.ServiceEventHandler.UpdateStockHandler;
import org.apache.openjpa.trader.client.ui.GridCellRenderer;
import org.apache.openjpa.trader.client.ui.ScrollableTable;
import org.apache.openjpa.trader.domain.LogStatement;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
/**
* One of the component widgets to display the server logs.
* The log messages are parsed to determine whether they are SQL statements and further
* categorized as SELECT, INSERT, UPDATE or DELETE to add a decorative CSS style.
* <br>
* CSS styles are
* <LI>sql-insert
* <LI>sql-update
* <LI>sql-select
* <LI>sql-delete
*
* @author Pinaki Poddar
*
*/
public class ServerLogPanel extends ScrollableTable<LogStatement>
implements AddTradableHandler, RemoveTradableHandler,
AddTradeHandler, UpdateStockHandler {
final OpenTrader session;
public static final String[] MARKERS_AND_STYLES = {"SELECT", "INSERT", "UPDATE", "DELETE"};
public ServerLogPanel(final OpenTrader session, final int w, final int h) {
super("Server Logs", w,h, false);
this.session = session;
session.registerHandler(ServiceEvent.TradableAdded.TYPE, this);
session.registerHandler(ServiceEvent.TradableRemoved.TYPE, this);
session.registerHandler(ServiceEvent.TradeCommitted.TYPE, this);
session.registerHandler(ServiceEvent.StockUpdated.TYPE, this);
setColumnHeader(0, "Context", "10%");
setColumnHeader(1, "Message", "90%");
setRenderer(0, new GridCellRenderer<LogStatement>() {
public Widget render(LogStatement log) {
return new Label(log.getContext());
}
});
setRenderer(1, new GridCellRenderer<LogStatement>() {
public Widget render(LogStatement log) {
return decorate(log.getMessage(), MARKERS_AND_STYLES);
}
});
}
HTML decorate(String s, String[] markersAndStyles) {
HTML html = new HTML(s);
String style = getStyle(s, MARKERS_AND_STYLES);
if (style != null)
html.addStyleName(style);
return html;
}
static String getStyle(String s, String[] markersAndStyles) {
String style = null;
for (int i = 0; i < markersAndStyles.length; i++) {
String marker = markersAndStyles[i];
int n = marker.length();
if (s.length() < n) {
continue;
}
String preamble = s.substring(0,n);
if (preamble.equalsIgnoreCase(marker)) {
style = "sql-"+marker.toLowerCase();
return style;
}
}
return null;
}
private void log() {
session.getService().getLog(new LoggingCallback());
}
/**
* ---------------------------------------------------------------------------------
* Service Event Response Management
*
* This widget receives all service event update and logs the corresponding server
* logs.
* ---------------------------------------------------------------------------------
*/
public void onTradableAdded(ServiceEvent.TradableAdded event) {
log();
}
public void onTradableRemoved(ServiceEvent.TradableRemoved event) {
log();
}
public void onTradeCommitted(ServiceEvent.TradeCommitted event) {
log();
}
public void onStockUpdated(ServiceEvent.StockUpdated event) {
log();
}
/**
* ---------------------------------------------------------------------------------
* Asynchronous RPC service callbacks
* ---------------------------------------------------------------------------------
*/
/**
* Unlike other callbacks, this callback on completion does not broadcast the log messages.
* Instead it simply inserts the message in its own tabular display.
*/
public class LoggingCallback implements AsyncCallback<List<LogStatement>> {
public void onFailure(Throwable caught) {
session.handleError(caught);
}
public void onSuccess(List<LogStatement> messages) {
if (messages == null)
return;
int N = messages.size();
for (int i = 0; i < N; i++) {
insert(messages.get(i));
}
}
}
}

View File

@ -0,0 +1,109 @@
/*
* 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.trader.client;
import org.apache.openjpa.trader.client.event.ServiceEvent;
import org.apache.openjpa.trader.client.event.ServiceEventHandler;
import org.apache.openjpa.trader.client.ui.GridCellRenderer;
import org.apache.openjpa.trader.client.ui.ScrollableTable;
import org.apache.openjpa.trader.domain.Ask;
import org.apache.openjpa.trader.domain.Bid;
import org.apache.openjpa.trader.domain.Tradable;
import org.apache.openjpa.trader.domain.Trade;
import org.apache.openjpa.trader.domain.Trader;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
/**
* One of the core component widgets to display the committed trades.
* The panel is configured for either {@link Ask} or {@link Bid} and
* accordingly it adjusts some of its labels or contents.
*
* @author Pinaki Poddar
*
*/
public class TradeHistoryPanel extends ScrollableTable<Trade>
implements ServiceEventHandler.AddTradeHandler {
private final OpenTrader session;
private final Class<? extends Tradable> _type;
public TradeHistoryPanel(final OpenTrader session, Class<? extends Tradable> type,
final int w, final int h) {
super("Stocks " + (type == Ask.class ? "sold" : "bought") + " by " + session.getName(), w,h, true);
this.session = session;
this._type = type;
session.registerHandler(ServiceEvent.TradeCommitted.TYPE, this);
setColumnHeader(0, "Stock", "20%");
setColumnHeader(1, "Price", "20%");
setColumnHeader(2, "Volume", "20%");
setColumnHeader(3, type == Ask.class ? "Buyer" : "Seller", "40%");
// Stock symbol
setRenderer(0, new GridCellRenderer<Trade>() {
public Widget render(Trade model) {
return new Label(model.getStock().getSymbol());
}
});
// Price of the trade
setRenderer(1, new GridCellRenderer<Trade>() {
public Widget render(Trade t) {
return FormatUtil.formatPrice(t.getPrice());
}
});
// Volume of the trade
setRenderer(2, new GridCellRenderer<Trade>() {
public Widget render(Trade t) {
return FormatUtil.formatVolume(t.getVolume());
}
});
// Counter Party
setRenderer(3, new GridCellRenderer<Trade>() {
public Widget render(Trade t) {
Trader cpty = session.getTrader().equals(t.getBuyer()) ? t.getSeller() : t.getBuyer();
return new Label(cpty.getName());
}
});
}
/**
* ---------------------------------------------------------------------------------
* Service Event Response Management
* ---------------------------------------------------------------------------------
*/
/**
* On receipt of the event determines if it is relevant for this instance.
* Because an instance display either the Trades sold or bought.
* If relevant then updates the display.
*/
public void onTradeCommitted(ServiceEvent.TradeCommitted event) {
Trader trader = session.getTrader();
if ((trader.equals(event.getPayload().getSeller()) && _type == Ask.class)
|| (trader.equals(event.getPayload().getBuyer()) && _type == Bid.class)) {
insert(event.getPayload());
}
}
}

View File

@ -0,0 +1,316 @@
/*
* 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.trader.client;
import java.util.List;
import org.apache.openjpa.trader.client.event.ServiceEvent;
import org.apache.openjpa.trader.client.event.ServiceEventHandler.UpdateStockHandler;
import org.apache.openjpa.trader.client.ui.HelpLink;
import org.apache.openjpa.trader.client.ui.MessageBox;
import org.apache.openjpa.trader.domain.Ask;
import org.apache.openjpa.trader.domain.Bid;
import org.apache.openjpa.trader.domain.Stock;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HasVerticalAlignment;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.TextBox;
/**
* This Widget allows the user to enter the details of a trade order (an Ask or
* Bid) and call the {@link TradingServiceAdapterAsync Trading Service} via asynchronous RPC
* callback to record the order.
* <br>
* The widget demonstrates the aspect where a displayed element can change either
* because other elements within the same widget are changing or because some external
* state is changing. For example, the gain/loss of a requested buy/sell offer can
* change as the user enters a different price. It can also change if the market
* price of the stock has changed externally. The former changes are handled by adding
* event handlers to the widget elements (such as onKeyUp in a text box), the later
* changes are notified by this widget registering to the {@link OpenTrader main application}.
*
*
* @author Pinaki Poddar
*
*/
class TradeOrderWindow extends FlexTable implements UpdateStockHandler {
private final OpenTrader session;
final ListBox symbols = new ListBox(false);
final Button ask = new Button("Ask");
final Button bid = new Button("Bid");
final TextBox marketPrice = new TextBox();
final TextBox userPrice = new TextBox();
final TextBox userVolume = new TextBox();
final TextBox margin = new TextBox();
final TextBox gain = new TextBox();
public TradeOrderWindow(final OpenTrader session, int w, int h) {
super();
this.session = session;
setPixelSize(w, h);
setStyleName("TradeOrderWindow");
session.registerHandler(ServiceEvent.StockUpdated.TYPE, this);
marketPrice.setReadOnly(true);
margin.setReadOnly(true);
gain.setReadOnly(true);
userPrice.setMaxLength(10);
userVolume.setMaxLength(10);
setCellPadding(-2);
setCellSpacing(-1);
setHTML(0, 0, "Stock");
setHTML(0, 1, "Market");
setHTML(0, 2, session.getName());
setHTML(0, 3, "Margin");
setHTML(0, 4, "Volume");
setHTML(0, 5, "Gain/Loss");
for (int i = 0; i < 5; i++) {
getCellFormatter().addStyleName(0, i, "TradingWindow-Label");
}
setWidget(1, 0, symbols);
setWidget(1, 1, marketPrice);
setWidget(1, 2, userPrice);
setWidget(1, 3, margin);
setWidget(1, 4, userVolume);
setWidget(1, 5, gain);
setWidget(2, 2, ask);
setWidget(2, 3, bid);
DOM.setStyleAttribute(getRowFormatter().getElement(0), "height", "4px");
userPrice.setFocus(true);
userPrice.setTabIndex(1);
userPrice.setTabIndex(2);
ask.setTabIndex(3);
bid.setTabIndex(4);
userPrice.addKeyUpHandler(new KeyUpHandler() {
public void onKeyUp(KeyUpEvent event) {
if (userPrice.getText().trim().length() == 0)
return;
double price = 0.0;
try {
price = Double.parseDouble(userPrice.getText());
} catch (NumberFormatException e) {
MessageBox.alert(userPrice.getText() + " must be a number");
return;
}
double diff = calculateDiff(price, getSelectedStock().getMarketPrice());
margin.setText(FormatUtil.priceFormat.format(diff));
gain.setText(FormatUtil.changeFormat.format(diff * Integer.parseInt(userVolume.getText())));
}
});
userVolume.addKeyUpHandler(new KeyUpHandler() {
public void onKeyUp(KeyUpEvent event) {
if (userVolume.getText().trim().length() == 0)
return;
int volume = 0;
try {
volume = Integer.parseInt(userVolume.getText());
} catch (NumberFormatException e) {
MessageBox.alert(userVolume.getText() + " must be a positive integer");
return;
}
if (volume <= 0) {
MessageBox.alert(userVolume.getText() + " must be a positive integer");
return;
}
double diff = Double.parseDouble(margin.getText());
gain.setText(FormatUtil.changeFormat.format(diff * volume));
}
});
List<Stock> stocks = session.getTradedStocks();
int n = stocks.size();
for (int i = 0; i < n; i++) {
symbols.addItem(stocks.get(i).getSymbol());
}
symbols.setSelectedIndex(0);
initialize(stocks.get(0), false);
symbols.addChangeHandler(new ChangeHandler() {
@Override
public void onChange(ChangeEvent event) {
Stock stock = getSelectedStock();
initialize(stock, false);
}
});
ask.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent ce) {
if (!validateData())
return;
session.getService().ask(session.getTrader(),
getSelectedStock(),
Integer.parseInt(userVolume.getText()),
Double.parseDouble(userPrice.getText()),
new AskCallback());
}
});
bid.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent ce) {
if (!validateData())
return;
session.getService().bid(session.getTrader(),
getSelectedStock(),
Integer.parseInt(userVolume.getText()),
Double.parseDouble(userPrice.getText()),
new BidCallback());
}
});
}
/**
* Sets the content of the widgets based on the given stock. The widget
* content depends on the current stock price as well as user entered
* values.
*
* @param stock
* @param retainUserValue
*/
void initialize(Stock stock, boolean retainUserValue) {
marketPrice.setText(FormatUtil.priceFormat.format(stock.getMarketPrice()));
if (retainUserValue && userPrice.getText().length() > 0) {
double diff = calculateDiff(Double.parseDouble(userPrice.getText()), stock.getMarketPrice());
margin.setText(FormatUtil.priceFormat.format(diff));
gain.setText(FormatUtil.priceFormat.format(diff * Integer.parseInt(userVolume.getText())));
} else {
userPrice.setText(FormatUtil.priceFormat.format(stock.getMarketPrice()));
userVolume.setText(FormatUtil.volumeFormat.format(100));
margin.setText(FormatUtil.priceFormat.format(0));
gain.setText(FormatUtil.priceFormat.format(0));
}
}
public void addHelp(final String url) {
HelpLink help = new HelpLink(url);
setWidget(0, 6, help);
getCellFormatter().setAlignment(0, 6,
HasHorizontalAlignment.ALIGN_RIGHT, HasVerticalAlignment.ALIGN_MIDDLE);
}
Stock getSelectedStock() {
return session.getTradedStocks().get(symbols.getSelectedIndex());
}
boolean validateData() {
try {
if (Double.parseDouble(userPrice.getText()) <= 0) {
MessageBox.alert("Price [" + userPrice.getText() + "] must be positive number");
return false;
}
} catch (NumberFormatException e) {
MessageBox.alert("Price [" + userPrice.getText() + "] must be a positive number");
return false;
}
try {
if (Integer.parseInt(userVolume.getText()) <= 0) {
MessageBox.alert("Volume [" + userVolume.getText() + "] must be a positive integer");
return false;
}
} catch (NumberFormatException e) {
MessageBox.alert("Volume [" + userVolume.getText() + "] must be a positive integer");
return false;
}
return true;
}
double calculateDiff(double p1, double p2) {
return truncate(Math.abs(p1-p2));
}
private static double truncate (double x){
double fract;
double whole;
if ( x > 0 ){
whole = Math.floor(x);
fract = Math.floor( (x - whole) * 100) / 100;
} else {
whole = Math.ceil(x);
fract = Math.ceil( (x - whole) * 100) / 100;
}
return whole + fract;
}
/**
* ---------------------------------------------------------------------------------
* Service Event Response Management
* ---------------------------------------------------------------------------------
*/
public void onStockUpdated(ServiceEvent.StockUpdated event) {
Stock updated = event.getPayload();
Stock current = getSelectedStock();
if (updated.equals(current)) {
initialize(updated, true);
}
}
/**
* ---------------------------------------------------------------------------------
* Asynchronous RPC service callbacks
* ---------------------------------------------------------------------------------
*/
/**
* Updates display once the offer to sell has been successfully placed.
*
*/
class AskCallback implements AsyncCallback<Ask> {
public void onSuccess(Ask result) {
session.fireEvent(new ServiceEvent.TradableAdded(result));
}
public void onFailure(Throwable caught) {
session.handleError(caught);
}
}
/**
* Updates display once the offer to buy has been successfully placed.
*
*/
class BidCallback implements AsyncCallback<Bid> {
public void onSuccess(Bid result) {
session.fireEvent(new ServiceEvent.TradableAdded(result));
}
public void onFailure(Throwable caught) {
session.handleError(caught);
}
}
}

View File

@ -0,0 +1,106 @@
/*
* 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.trader.client;
import java.sql.Timestamp;
import java.util.List;
import org.apache.openjpa.trader.domain.Ask;
import org.apache.openjpa.trader.domain.Bid;
import org.apache.openjpa.trader.domain.LogStatement;
import org.apache.openjpa.trader.domain.Match;
import org.apache.openjpa.trader.domain.Stock;
import org.apache.openjpa.trader.domain.Tradable;
import org.apache.openjpa.trader.domain.Trade;
import org.apache.openjpa.trader.domain.Trader;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
/**
* The client side stub for the RPC service.
* <br>
* This is a delegating interface to the original {@link TradingService} interface. This delegation
* pattern serves several purposes
* <LI> a) the delegator adds RuntimeException to each of the original service methods.
* The original {@link TradingService} has not defined the exception in its method signature,
* However, the actual implementation of {@link TradingService} is based on
* JPA and hence will throw <code>javax.persistence.PersistenceException</code>. To propagate
* these exceptions to the client, these exceptions need to be translated for serialization by
* GWT compiler and will bring in heavier dependency.
* <br>
* On the other hand, GWT will not propagate a non-translatable exception to the client,
* thereby making develop-debug cycles harder.
* <LI> b) GWT requires the service interface to extend GWT-defined
* <code>com.google.gwt.user.client.rpc.RemoteService</code>. But the discipline used for this
* application dictates that the <em>service interface</em> be independent of either
* how it is implemented or how it will be accessed. That is why original {@link TradingService}
* definition neither depends on <code>javax.persistence.*</code> nor on <code>com.google.gwt.*</code>.
* <p>
* Because the interface is delegated, it is not necessary to have the same method name or
* even signature. It may not have to declare all the original service interface methods either.
* <p>
*
* Any type appearing in this interface must be serializable per GWT requirement.
*/
/**
* This <code>@RemoteServiceRelativePath</code> annotation defines the relative URL of the
* deployed servlet. It appears in <code>web.xml</code> as:
* <pre>
* &lt;servlet-mapping>
* &lt;servlet-name>opentrader&lt;/servlet-name>
* &lt;url-pattern>/opentrader/<b>trade</b>&lt;/url-pattern>
* &lt;/servlet-mapping>
* </pre>
* <p>
* The servlet name matches the name used in module descriptor <code>OpenTrader.gwt.xml</code> as
* <pre>
* &lt;module rename-to='opentrader'>
* </pre>
*/
@RemoteServiceRelativePath("trade")
public interface TradingServiceAdapter extends RemoteService {
Trader login(String name)
throws RuntimeException;
List<Stock> getStocks()
throws RuntimeException;
Ask ask(Trader trader, Stock stock, int volume, double price)
throws RuntimeException;
Bid bid(Trader trader, Stock stock, int volume, double price)
throws RuntimeException;
Tradable withdraw(Tradable t)
throws RuntimeException;
Tradable refresh(Tradable t)
throws RuntimeException;
List<Match> matchBid(Bid bid)
throws RuntimeException;
List<Match> matchAsk(Ask ask)
throws RuntimeException;
Trade trade(Match match)
throws RuntimeException;
List<Trade> getTrades(Timestamp from, Timestamp to)
throws RuntimeException;
List<Trade> getTrades(Trader trader, Boolean boughtOrsold, Timestamp from, Timestamp to)
throws RuntimeException;
Stock getStock(String symbol)
throws RuntimeException;
List<LogStatement> getLog()
throws RuntimeException;
}

View File

@ -0,0 +1,71 @@
/*
* 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.trader.client;
import java.sql.Timestamp;
import java.util.List;
import org.apache.openjpa.trader.domain.Ask;
import org.apache.openjpa.trader.domain.Bid;
import org.apache.openjpa.trader.domain.LogStatement;
import org.apache.openjpa.trader.domain.Match;
import org.apache.openjpa.trader.domain.Stock;
import org.apache.openjpa.trader.domain.Tradable;
import org.apache.openjpa.trader.domain.Trade;
import org.apache.openjpa.trader.domain.Trader;
import com.google.gwt.user.client.rpc.AsyncCallback;
/**
* The asynchronous counterpart of the interface.
* The equivalence between this asynchronous interface and its {@link TradingServiceAdapter
* synchronous version} is validated during GWT compilation.
*
* @author Pinaki Poddar
*
*/
public interface TradingServiceAdapterAsync {
void ask(Trader trader, Stock stock, int volume, double price, AsyncCallback<Ask> callback);
void bid(Trader trader, Stock stock, int volume, double price, AsyncCallback<Bid> callback);
void getStock(String symbol, AsyncCallback<Stock> callback);
void getStocks(AsyncCallback<List<Stock>> callback);
void getTrades(Timestamp from, Timestamp to, AsyncCallback<List<Trade>> callback);
void getTrades(Trader trader, Boolean boughtOrsold, Timestamp from, Timestamp to,
AsyncCallback<List<Trade>> callback);
void login(String trader, AsyncCallback<Trader> callback);
void matchAsk(Ask ask, AsyncCallback<List<Match>> callback);
void matchBid(Bid bid, AsyncCallback<List<Match>> callback);
void trade(Match match, AsyncCallback<Trade> callback);
void getLog(AsyncCallback<List<LogStatement>> callback);
void withdraw(Tradable t, AsyncCallback<Tradable> callback);
void refresh(Tradable t, AsyncCallback<Tradable> callback);
}

View File

@ -0,0 +1,264 @@
/*
* 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.trader.client;
import java.util.List;
import org.apache.openjpa.trader.client.event.ServiceEvent;
import org.apache.openjpa.trader.client.event.ServiceEventHandler;
import org.apache.openjpa.trader.client.ui.GridCellRenderer;
import org.apache.openjpa.trader.client.ui.ScrollableTable;
import org.apache.openjpa.trader.domain.Ask;
import org.apache.openjpa.trader.domain.Bid;
import org.apache.openjpa.trader.domain.Match;
import org.apache.openjpa.trader.domain.Stock;
import org.apache.openjpa.trader.domain.Tradable;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
/**
* Trading Window allows the user to buy/sell a {@link Tradable tradable} or withdraw it.
* <br>
* This widget demonstrates combination of both read-only and updatable visual elements
* as well as active widgets such as a button.
* <br>
* Both the user actions (such as when a tradable is withdrawn) or other events such
* as a Stock price change changes the expected gain/loss of a tradable,
* <br>
* The complexity arises from the fact that a displayed tradable may have been consumed
* by a matching tradable in another session. A tradable undergoes a state transition when
* it is traded. Thus the displayed tradable can be an inconsistent state than its original
* state in the server. Though all the displayed tradables are periodically refreshed, the
* latency still exists.
*
*
* @author Pinaki Poddar
*
*/
public class TradingWindow extends ScrollableTable<Tradable>
implements ServiceEventHandler.AddTradableHandler,
ServiceEventHandler.RemoveTradableHandler,
ServiceEventHandler.UpdateStockHandler {
private final OpenTrader session;
private Timer refreshTimer;
private int refreshInterval = 60*1000;
public TradingWindow(final OpenTrader session, final int w, final int h) {
super("Trading Window for " + session.getTrader().getName(), w, h, true);
this.session = session;
session.registerHandler(ServiceEvent.TradableAdded.TYPE, this);
session.registerHandler(ServiceEvent.TradableRemoved.TYPE, this);
session.registerHandler(ServiceEvent.StockUpdated.TYPE, this);
setColumnHeader(0, "Stock", "15%");
setColumnHeader(1, "Market", "15%");
setColumnHeader(2, "Price", "15%");
setColumnHeader(3, "Volume", "15%");
setColumnHeader(4, "Gain/Loss", "15%");
setColumnHeader(5, "Buy/Sell", "15%");
setColumnHeader(6, "Withdraw", "15%");
setRenderer(0, new GridCellRenderer<Tradable>() {
public Widget render(Tradable model) {
return new Label(model.getStock().getSymbol());
}
});
// Market Price as changing Label
setRenderer(1, new GridCellRenderer<Tradable>() {
public Widget render(Tradable model) {
return FormatUtil.formatPrice(model.getStock().getMarketPrice());
}
});
// Ask/Bid Price as Label
setRenderer(2, new GridCellRenderer<Tradable>() {
public Widget render(Tradable model) {
return FormatUtil.formatPrice(model.getPrice());
}
});
// Ask/Bid Volume as Label
setRenderer(3, new GridCellRenderer<Tradable>() {
public Widget render(Tradable model) {
return FormatUtil.formatVolume(model.getVolume());
}
});
// Gain or loss
setRenderer(4, new GridCellRenderer<Tradable>() {
public Widget render(Tradable t) {
return FormatUtil.formatChange(t.getGain());
}
});
// Buy/Sell Button
setRenderer(5, new GridCellRenderer<Tradable>() {
public Widget render(Tradable t) {
String action = t instanceof Ask ? "Sell" : "Buy";
Button button = new Button(action);
button.addClickHandler(new MatchCallback(t));
return button;
}
});
// Withdraw button
setRenderer(6, new GridCellRenderer<Tradable>() {
public Widget render(Tradable t) {
Button button = new Button("Withdraw");
button.addClickHandler(new WithdrawCallback(t));
return button;
}
});
}
/**
* Starts to run a period update of the tradables from the server.
*/
public void startTradableRefresher() {
if (refreshTimer != null)
return;
// Setup timer to refresh list automatically.
Timer refreshTimer = new Timer() {
@Override
public void run() {
int n = getRowCount();
for (int i = 0; i < n; i++) {
Tradable t = get(i);
if (t != null) {
session.getService().refresh(t, new RefreshTradableCallback(t));
}
}
}
};
refreshTimer.run();
refreshTimer.scheduleRepeating(refreshInterval);
}
public void stopTradableRefresher() {
if (refreshTimer == null)
return;
refreshTimer.cancel();
refreshTimer = null;
}
@Override
public void onTradableAdded(ServiceEvent.TradableAdded event) {
insert(event.getPayload());
}
@Override
public void onTradableRemoved(ServiceEvent.TradableRemoved event) {
remove(event.getPayload());
}
@Override
public void onStockUpdated(ServiceEvent.StockUpdated event) {
int n = getRowCount();
Stock updatedStock = event.getPayload();
for (int i = 0; i < n; i++) {
Tradable t = get(i);
if (updatedStock.equals(t.getStock())) {
t.updateStock(updatedStock);
update(t, new Integer[]{1,4});
}
}
}
class WithdrawCallback implements AsyncCallback<Tradable>, ClickHandler {
private final Tradable tradable;
WithdrawCallback(Tradable source) {
tradable = source;
}
public void onFailure(Throwable caught) {
session.handleError(caught);
}
public void onSuccess(Tradable result) {
session.fireEvent(new ServiceEvent.TradableRemoved(result));
}
@Override
public void onClick(ClickEvent event) {
session.getService().withdraw(tradable, this);
}
}
public class MatchCallback implements AsyncCallback<List<Match>>, ClickHandler {
final Tradable tradable;
public MatchCallback(Tradable tradable) {
super();
this.tradable = tradable;
}
public void onFailure(Throwable caught) {
session.handleError(caught);
}
public void onSuccess(List<Match> result) {
new MatchWindow(session, tradable, result).center();
}
@Override
public void onClick(ClickEvent event) {
if (tradable instanceof Ask) {
session.getService().matchAsk((Ask) tradable, this);
} else {
session.getService().matchBid((Bid) tradable, this);
}
}
}
public class RefreshTradableCallback implements AsyncCallback<Tradable> {
final Tradable tradable;
public RefreshTradableCallback(Tradable tradable) {
super();
this.tradable = tradable;
}
@Override
public void onSuccess(Tradable result) {
if (result == null) {
remove(tradable);
} else if (result.isTraded()) {
remove(result);
session.fireEvent(new ServiceEvent.TradeCommitted(result.getTrade()));
} else {
update(result, new Integer[]{1,4});
}
}
@Override
public void onFailure(Throwable t) {
session.handleError(t);
}
}
}

View File

@ -0,0 +1,130 @@
/*
* 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.trader.client.event;
import org.apache.openjpa.trader.client.OpenTrader;
import org.apache.openjpa.trader.client.event.ServiceEventHandler.UpdateStockHandler;
import org.apache.openjpa.trader.domain.Stock;
import org.apache.openjpa.trader.domain.Tradable;
import org.apache.openjpa.trader.domain.Trade;
import com.google.gwt.event.shared.EventHandler;
import com.google.gwt.event.shared.GwtEvent;
/**
* Specialization of GWTEvent (that represent mouse/keybord events and DOM events)
* for service related events such as a new trade has been committed or a tradable
* is consumed. Each service event carries a payload and one or more handlers can
* register interest in that event via the
* {@link OpenTrader#registerHandler(com.google.gwt.event.shared.GwtEvent.Type, EventHandler)
* application controller}.
*
* @author Pinaki Poddar
*
* @param <T>
* @param <H>
*/
public abstract class ServiceEvent<T, H extends EventHandler> extends GwtEvent<H> {
private final T payload;
protected ServiceEvent(T data) {
payload = data;
}
public final T getPayload() {
return payload;
}
public static class TradableAdded extends ServiceEvent<Tradable, ServiceEventHandler.AddTradableHandler> {
public static Type<ServiceEventHandler.AddTradableHandler> TYPE = new Type<ServiceEventHandler.AddTradableHandler>();
public TradableAdded(Tradable tradable) {
super(tradable);
}
@Override
protected void dispatch(ServiceEventHandler.AddTradableHandler handler) {
handler.onTradableAdded(this);
}
@Override
public com.google.gwt.event.shared.GwtEvent.Type<ServiceEventHandler.AddTradableHandler> getAssociatedType() {
return TYPE;
}
}
public static class TradableRemoved extends ServiceEvent<Tradable, ServiceEventHandler.RemoveTradableHandler> {
public static Type<ServiceEventHandler.RemoveTradableHandler> TYPE = new Type<ServiceEventHandler.RemoveTradableHandler>();
public TradableRemoved(Tradable tradable) {
super(tradable);
}
@Override
protected void dispatch(ServiceEventHandler.RemoveTradableHandler handler) {
handler.onTradableRemoved(this);
}
@Override
public com.google.gwt.event.shared.GwtEvent.Type<ServiceEventHandler.RemoveTradableHandler> getAssociatedType() {
return TYPE;
}
}
public static class StockUpdated extends ServiceEvent<Stock,UpdateStockHandler> {
public static Type<UpdateStockHandler> TYPE = new Type<UpdateStockHandler>();
public StockUpdated(Stock stock) {
super(stock);
}
@Override
protected void dispatch(UpdateStockHandler handler) {
handler.onStockUpdated(this);
}
@Override
public com.google.gwt.event.shared.GwtEvent.Type<UpdateStockHandler> getAssociatedType() {
return TYPE;
}
}
public static class TradeCommitted extends ServiceEvent<Trade, ServiceEventHandler.AddTradeHandler> {
public static Type<ServiceEventHandler.AddTradeHandler> TYPE = new Type<ServiceEventHandler.AddTradeHandler>();
public TradeCommitted(Trade trade) {
super(trade);
}
@Override
protected void dispatch(ServiceEventHandler.AddTradeHandler handler) {
handler.onTradeCommitted(this);
}
@Override
public com.google.gwt.event.shared.GwtEvent.Type<ServiceEventHandler.AddTradeHandler> getAssociatedType() {
return TYPE;
}
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.trader.client.event;
import com.google.gwt.event.shared.EventHandler;
/**
* The contract to handle {@link ServiceEvent service events}.
*
*
* @author Pinaki Poddar
*
*/
public interface ServiceEventHandler {
public interface AddTradableHandler extends EventHandler {
public void onTradableAdded(ServiceEvent.TradableAdded event);
}
public static interface AddTradeHandler extends EventHandler {
public void onTradeCommitted(ServiceEvent.TradeCommitted event);
}
public static interface RemoveTradableHandler extends EventHandler {
public void onTradableRemoved(ServiceEvent.TradableRemoved event);
}
public static interface UpdateStockHandler extends EventHandler {
public void onStockUpdated(ServiceEvent.StockUpdated event);
}
}

View File

@ -0,0 +1,121 @@
/*
* 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.trader.client.ui;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;
/**
* A singleton Error Dialog to show the main error message and optional stack traces.
* <br>
* CSS Style names
* <LI>errorDialog-caption:
* <LI>errorDialog-message:
*
* @author Pinaki Poddar
*
*/
public class ErrorDialog extends PopupPanel {
private FlexTable table;
private HTML header;
private Button close;
private Tree tree;
private static ErrorDialog _instance = new ErrorDialog();
private static final String STYLE_CAPTION = "errorDialog-caption";
private static final String STYLE_MESSAGE = "errorDialog-message";
private ErrorDialog() {
super(false, true);
setAnimationEnabled(true);
setGlassEnabled(true);
setVisible(false);
header = new HTML();
header.addStyleName(STYLE_CAPTION);
close = new Button("x");
close.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
hide();
}
});
tree = new Tree();
ScrollPanel scroll = new ScrollPanel();
scroll.setSize("600px", "200px");
scroll.add(tree);
DOM.setStyleAttribute(scroll.getElement(), "border", "1px");
table = new FlexTable();
table.setWidget(0, 0, header);
table.setWidget(0, 1, close);
table.setWidget(1, 0, scroll);
table.getFlexCellFormatter().setColSpan(1, 0, 2);
table.getCellFormatter().setHorizontalAlignment(0, 1, HasHorizontalAlignment.ALIGN_RIGHT);
setWidget(table);
}
public static void showError(Throwable t) {
_instance.populate(t);
_instance.center();
}
private void populate(Throwable t) {
header.setHTML(t.getClass().getName());
tree.clear();
tree = addStackTrace(t);
}
private Tree addStackTrace(Throwable t) {
TreeItem root = new TreeItem(t.getClass().getName());
root.addItem(createMessageLabel(t));
StackTraceElement[] traces = t.getStackTrace();
for (int i = 0; i < traces.length; i++) {
root.addItem(createStackTrace(traces[i]));
}
tree.addItem(root);
Throwable cause = t.getCause();
if (cause == null || cause == t) {
return tree;
}
return addStackTrace(cause);
}
Label createMessageLabel(Throwable t) {
HTML label = new HTML(t.getMessage());
label.addStyleName(STYLE_MESSAGE);
return label;
}
Label createStackTrace(StackTraceElement trace) {
HTML label = new HTML(trace.toString());
label.addStyleName(STYLE_MESSAGE);
return label;
}
}

View File

@ -0,0 +1,63 @@
/*
* 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.trader.client.ui;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Widget;
/**
* Fun stuff to fade-in, fade-out a changing label.
*
* @author Pinaki Poddar
*
*/
public class FadeEffect extends Timer {
static final String OPACITY = "opacity";
final Element elem;
final Widget html;
double opacity = 0.0;
double delta = 0.01;
int sign = -1;
FadeEffect(Widget html, boolean appear) {
this.html = html;
elem = html.getElement();
sign = appear ? 1 : -1;
opacity = appear ? 0.0 : 1.0;
}
@Override
public void run() {
DOM.setStyleAttribute(elem, OPACITY, "" + opacity);
opacity = opacity + sign * delta;
if (ended()) {
cancel();
}
}
boolean ended() {
if (sign == -1) {
return opacity <= 0.0;
} else {
return opacity >= 1.0;
}
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.trader.client.ui;
import com.google.gwt.user.client.ui.Widget;
/**
* Protocol to render a Grid Cell.
*
* @author Pinaki Poddar
*
* @param <T>
*/
public interface GridCellRenderer<T> {
/**
* Create an widget for the part of data this render is rendering.
*
* @param data the entire data represented in this row.
* @return a widget such as a Label or HTML to represent a part of the data.
*/
Widget render(T data);
}

View File

@ -0,0 +1,94 @@
/*
* 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.trader.client.ui;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Frame;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
/**
* An anchor that pops up a min-browser with some hopefully helpful text.
*
* @author Pinaki Poddar
*
*/
public class HelpLink extends Anchor implements ClickHandler {
private final String url;
private static HelpWindow window;
public HelpLink(String url) {
super("Help", url, "Help");
this.url = url;
addClickHandler(this);
if (window == null) {
window = new HelpWindow();
}
addStyleName("help");
}
@Override
public void onClick(ClickEvent event) {
window.showHelp(url);
event.preventDefault();
}
public class HelpWindow extends PopupPanel implements PopupPanel.PositionCallback {
private final Frame frame;
public HelpWindow() {
super(true);
setAnimationEnabled(true);
setAutoHideEnabled(true);
setModal(false);
VerticalPanel panel = new VerticalPanel();
frame = new Frame();
frame.setPixelSize(400, 300);
Button close = new Button("close");
close.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
hide();
}
});
panel.add(frame);
panel.add(close);
setWidget(panel);
setVisible(true);
}
public void showHelp(String url) {
frame.setUrl(url);
super.setPopupPositionAndShow(this);
}
public void setPosition(int offsetWidth, int offsetHeight) {
int left = Window.getClientWidth() - getWidget().getOffsetWidth() - 100;
int top = 40;
setPopupPosition(left, top);
}
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.trader.client.ui;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.DockPanel;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
/**
* A non-modal, pop-up message box.
* <br>
* CSS Styles:
* <LI>messageBox
* <LI>messageBox-content
* <LI>messageBox-caption
* <LI>
* The
* @author Pinaki Poddar
*
*/
public class MessageBox extends PopupPanel {
private static MessageBox _popup = new MessageBox();
private final HTML header;
private final Label message;
private MessageBox() {
super(false, true);
setAnimationEnabled(true);
DockPanel panel = new DockPanel();
panel.setStyleName("messageBox");
panel.setHorizontalAlignment(DockPanel.ALIGN_CENTER);
header = new HTML();
header.addStyleName("messageBox-caption");
Button close = new Button("OK");
close.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
hide();
}
});
close.setEnabled(true);
close.setFocus(true);
message = new Label();
message.addStyleName("messageBox-content");
panel.add(header, DockPanel.NORTH);
panel.add(close, DockPanel.SOUTH);
panel.add(message, DockPanel.CENTER);
setWidget(panel);
}
public static void alert(String message) {
alert("Alert", message);
}
public static void alert(String header, String message) {
_popup.header.setText(message);
_popup.message.setText(message);
_popup.center();
}
}

View File

@ -0,0 +1,92 @@
/*
* 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.trader.client.ui;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
/**
* Fun stuff - a progress monitor.
* CSS Styles
* <LI>progressMonitor
* <LI>progressMonitor-caption
* @author Pinaki Poddar
*
*/
public class ProgressMonitor extends Timer {
static final String OPACITY = "opacity";
private final String highOpacity = "1.0";
private final String lowOpacity = "0.2";
private volatile int current = 0;
private final FlexTable bar;
private final HTML header;
private final int N = 10;
private final PopupPanel popup;
private final static ProgressMonitor _instance = new ProgressMonitor();
private ProgressMonitor() {
popup = new PopupPanel();
popup.addStyleName("progressMonitor");
header = new HTML();
bar = new FlexTable();
bar.setCellSpacing(1);
bar.setWidget(0, 0, header);
header.addStyleName("progressMonitor-caption");
for (int i = 0; i < N; i++) {
Label box = new Label();
box.setSize("10px", "20px");
DOM.setStyleAttribute(box.getElement(), "backgroundColor", "black");
DOM.setStyleAttribute(box.getElement(), OPACITY, lowOpacity);
bar.setWidget(1, i, box);
}
bar.getFlexCellFormatter().setColSpan(0, 0, N);
popup.add(bar);
}
public static void showProgress(String caption) {
_instance.header.setText(caption);
_instance.popup.center();
_instance.scheduleRepeating(10);
_instance.run();
}
public static void stop() {
_instance.cancel();
_instance.popup.hide();
}
@Override
public void run() {
Element elem = bar.getWidget(1, current).getElement();
DOM.setStyleAttribute(elem, OPACITY, lowOpacity);
current++;
current = current%N;
elem = bar.getWidget(1, current).getElement();
DOM.setStyleAttribute(elem, OPACITY, highOpacity);
}
}

View File

@ -0,0 +1,298 @@
/*
* 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.trader.client.ui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
/**
* A composite widget combines a data table, a table header, a scrollbar,
* and a caption and a {@link HelpLink help anchor}.
* <br>
* Each row of the data table displays an instance of type T. How the instance
* is displayed in controlled by a set of {@link GridCellRenderer renderers}
* attached to each column.
* <br>
* The styles used
* <LI>table-caption : for the caption
* <LI>column-header : for the column headers
* <LI>row-odd : for odd numbered rows
* <LI>row-even : for even numbered rows
*
*
* @author Pinaki Poddar
*
* @param <T> the type of data being displayed.
*/
public class ScrollableTable<T> extends Composite {
private FlexTable _main;
private Grid _caption;
private FlexTable _header;
private ScrollPanel _scroll;
private List<GridCellRenderer<T>> _renderers;
private List<T> _rows;
private boolean _stripeRows;
private static final String STYLE_CAPTION = "table-caption";
private static final String STYLE_HEADER = "column-header";
private static final String STYLE_MAIN = "table";
private static final String ROW_EVEN = "row-even";
private static final String ROW_ODD = "row-odd";
/**
* Create a scrollable table with the given caption and given pixel dimension.
* The table will not be backed by a data storage model.
*
* @param caption of the table
* @param w width in pixel
* @param h height in pixel
*/
public ScrollableTable(String caption, int w, int h) {
this(caption, w+"px", h+"px", false);
}
/**
* Create a scrollable table with the given caption and given pixel dimension.
* The table will not be backed by a data storage model.
*
* @param caption of the table
* @param w width in pixel
* @param h height in pixel
* @param updatable whether the table data will be backed by a storage such
* that row can be updated rather than always inserted
*/
public ScrollableTable(String caption, int w, int h, boolean updatable) {
this(caption, w+"px", h+"px", updatable);
}
/**
* Create a scrollable table with the given caption and given dimension.
*
* @param caption of the table
* @param w width in given unit
* @param h height in given unit
* @param updatable whether the table data will be backed by a storage such
* that row can be updated rather than always inserted
*/
public ScrollableTable(String caption, String w, String h, boolean updatable) {
super();
_renderers = new ArrayList<GridCellRenderer<T>>();
VerticalPanel vert = new VerticalPanel();
vert.setSpacing(0);
_caption = new Grid(1,2);
_caption.setWidth("100%");
setCaption(caption);
vert.add(_caption);
_header = new FlexTable();
_header.addStyleName(STYLE_HEADER);
_header.setWidth("100%");
_main = new FlexTable();
_main.addStyleName(STYLE_MAIN);
_main.setWidth("100%");
_main.setBorderWidth(0);
_scroll = new ScrollPanel();
_scroll.setSize(w, h);
_scroll.setAlwaysShowScrollBars(true);
_scroll.add(_main);
vert.add(_header);
vert.add(_scroll);
if (updatable) {
_rows = new ArrayList<T>();
}
initWidget(vert);
}
@SuppressWarnings("unchecked")
public List<T> getModel() {
return _rows == null ? Collections.EMPTY_LIST : Collections.unmodifiableList(_rows);
}
public void setCaption(String str) {
HTML caption = new HTML(str);
caption.addStyleName(STYLE_CAPTION);
_caption.setWidget(0, 0, caption);
}
public void setSize(int w, int h) {
int dh = 0;
if (_caption != null) {
dh += _caption.getOffsetHeight();
}
dh += _header.getOffsetHeight();
_scroll.setPixelSize(w, h-dh);
}
public void setRenderer(int column, GridCellRenderer<T> r) {
if (column > _renderers.size()) {
for (int i = _renderers.size(); i <= column; i++) {
_renderers.add(null);
}
_renderers.add(r);
} else if (column == _renderers.size()) {
_renderers.add(r);
} else {
_renderers.set(column, r);
}
}
public void setColumnHeader(int column, String txt, String width) {
HTML header = new HTML(txt);
header.addStyleName(STYLE_HEADER);
_header.setWidget(0, column, header);
_main.getColumnFormatter().setWidth(column, width);
}
public void setStripeRows(boolean stripe) {
_stripeRows = stripe;
}
public boolean isStripeRows() {
return _stripeRows;
}
public void insert(T data) {
setRow(_main.getRowCount(), data, null);
}
public void remove(T data) {
int i = findRow(data);
if (i != -1) {
_main.removeRow(i);
_rows.remove(i);
}
}
/**
* Update entire row.
*/
public void update(T data, Integer[] columns) {
int i = findRow(data);
if (i != -1) {
setRow(i, data, columns);
} else {
insert(data);
}
}
public void updateCell(int row, int column, Widget widget, boolean animate) {
if (animate) {
FadeEffect fadeOut = new FadeEffect(_main.getWidget(row, column), false);
fadeOut.scheduleRepeating(10);
DOM.setElementAttribute(widget.getElement(), "opacity", "0.0");
_main.setWidget(row, column, widget);
FadeEffect fadeIn = new FadeEffect(_main.getWidget(row, column), true);
fadeIn.scheduleRepeating(10);
} else {
_main.setWidget(row, column, widget);
}
}
public int getRowCount() {
return _main.getRowCount();
}
/**
* Sets the cells of an existing row.
* Calls each renderer.
* @param row
* @param data
*/
private void setRow(int row, T data, Integer[] columns) {
if (_rows != null) {
if (row < 0 || row >= _rows.size())
_rows.add(data);
else
_rows.set(row, data);
}
for (int i = 0; i < _renderers.size(); i++) {
GridCellRenderer<T> r = _renderers.get(i);
if (r == null)
continue;
if (containsColumn(columns, i)) {
Widget widget = r.render(data);
if (widget != null)
_main.setWidget(row, i, widget);
}
}
if (isStripeRows()) {
_main.getRowFormatter().setStylePrimaryName(row, row%2 == 0? ROW_EVEN : ROW_ODD);
}
_scroll.scrollToBottom();
}
public int findRow(T data) {
if (_rows == null || data == null)
return -1;
return _rows.indexOf(data);
}
private boolean containsColumn(Integer[] columns, int i) {
if (columns == null)
return true;
for (int j = 0; j < columns.length; j++) {
if (columns[j] == i)
return true;
}
return false;
}
public T get(int i) {
if (_rows == null || i < 0 || i >= _rows.size())
return null;
return _rows.get(i);
}
public void scrollToBottom() {
_scroll.scrollToBottom();
}
public void addHelp(final String url) {
if (_caption == null) {
return;
}
_caption.getColumnFormatter().setWidth(0, "95%");
HelpLink help = new HelpLink(url);
_caption.setWidget(0, 1, help);
_caption.getCellFormatter().setHorizontalAlignment(0, 1, HasHorizontalAlignment.ALIGN_RIGHT);
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.trader.domain;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
/**
* An offer to sell a financial instrument.
* The only mutable state of an offer is its expiration.
* But that state is also mutable only once and is not reversible.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
@Entity
public class Ask extends Tradable {
/**
* The trader who has made the offer.
* Must never be null.
*/
@ManyToOne(cascade={CascadeType.MERGE,CascadeType.DETACH},optional=false)
private Trader seller;
/**
* A no-arg constructor to comply to both GWT compiler and OpenJPA
* bytecode enhancer.
*/
protected Ask() {
super();
}
/**
* Real constructor populates the essential properties.
*
* @param trader the trader who made this offer. Must not be null.
* @param stock the underlying instrument. Must not be null.
* @param price the offered price to sell. Must be positive.
* @param volume the number of instruments to sell. Must be positive.
*/
public Ask(Trader trader, Stock stock, double price, int volume) {
super(stock, price, volume);
if (trader == null)
throw new NullPointerException("Can not create Ask with null trader");
this.seller = trader;
}
/**
* Gets the trader who made this offer to sell.
*
* @return the trader who made this offer. Never null.
*/
public Trader getSeller() {
return seller;
}
public double getGain() {
return (getPrice() - getStock().getMarketPrice())*getVolume();
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.trader.domain;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.ManyToOne;
/**
* An offer to buy a financial instrument.
* The only mutable state of an offer is its expiration.
* But that state is also mutable only once and is not reversible.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
@Entity
public class Bid extends Tradable {
/**
* The trader who has made the offer.
* Must never be null.
*/
@ManyToOne(cascade={CascadeType.MERGE,CascadeType.DETACH},optional=false)
private Trader buyer;
/**
* A no-arg constructor to comply to both GWT compiler and OpenJPA
* bytecode enhancer.
*/
protected Bid() {
super();
}
/**
* Real constructor populates the essential properties.
*
* @param trader the trader who made this offer. Must not be null.
* @param stock the underlying instrument. Must not be null.
* @param price the offered price to buy. Must be positive.
* @param volume the number of instruments to buy. Must be positive.
*/
public Bid(Trader trader, Stock stock, double price, int volume) {
super(stock, price, volume);
if (trader == null)
throw new NullPointerException("Can not create Bid with null trader");
this.buyer = trader;
}
/**
* Gets the trader who made this offer.
*
* @return the trader who made this offer. Never null.
*/
public Trader getBuyer() {
return buyer;
}
public double getGain() {
return (getStock().getMarketPrice() - getPrice())*getVolume();
}
}

View File

@ -0,0 +1,67 @@
/*
* 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.trader.domain;
import java.io.Serializable;
@SuppressWarnings("serial")
public class LogStatement implements Serializable {
private String level;
private String context;
private String thread;
private String channel;
private String message;
protected LogStatement() {
}
public LogStatement(String level, String context, String thread, String channel, String message) {
super();
this.level = level;
this.context = context;
this.thread = thread;
this.channel = channel;
this.message = message;
}
public String getLevel() {
return level;
}
public String getContext() {
return context;
}
public String getThread() {
return thread;
}
public String getChannel() {
return channel;
}
public String getMessage() {
return message;
}
public String toString() {
return level + " [" + context + "] [" + thread + "] [" + channel + "] :" + message;
}
}

View File

@ -0,0 +1,86 @@
/*
* 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.trader.domain;
import java.io.Serializable;
/**
* A pair of matching offer to {@linkplain Bid buy} and {@linkplain Ask sell}.
* This is <em>not</em> a persistent entity. But it is in the persistent domain
* because it is often is the result of query.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class Match implements Serializable {
private Ask ask;
private Bid bid;
/**
* A no-arg constructor to comply to GWT compiler.
*/
protected Match() {
}
/**
* Constructs a pair with matching offers.
* @param a the offer to sell. Must not be null.
* @param b the offer to buy. Must not be null.
*/
public Match(Ask a, Bid b) {
if (a == null)
throw new NullPointerException("Can not create Match with null Ask");
if (b == null)
throw new NullPointerException("Can not create Match with null Bid");
if (a.getSeller().equals(b.getBuyer())) {
throw new NullPointerException("Can not create Match with same Trader "
+ a.getSeller() + " for Ask and Bid");
}
if (a.getPrice() > b.getPrice()) {
throw new IllegalArgumentException("Ask price " + a.getPrice() + " is greater than "
+ " Bid price " + b.getPrice());
}
ask = a;
bid = b;
}
/**
* Gets the matching offer to sell.
*
* @return the matching offer to sell. Never null.
*/
public Ask getAsk() {
return ask;
}
/**
* Gets the matching offer to buy.
*
* @return the matching offer to buy. Never null.
*/
public Bid getBid() {
return bid;
}
public String toString() {
return "Match ["+ ask + " and " + bid + "]";
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.trader.domain;
/**
* Designates the business sector of a financial instrument.
*
* @author Pinaki Poddar
*
*/
public enum Sector {
HEALTHCARE,
FINACE,
INFRASTRUCTURE
}

View File

@ -0,0 +1,124 @@
/*
* 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.trader.domain;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
/**
* A stock is a typical financial instrument that is bought and sold by {@linkplain Trader}.
* A stock is mostly an immutable entity, except its price.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
@Entity
public class Stock implements Serializable {
/**
* The primary identity of a Stock.
* The uniqueness of the primary keys are often warranted by the persistence provider.
* In this case, unique of a Stock symbol is ensured by an external agency.
*/
@Id
private String symbol;
/**
* The name of the company represented by this financial instrument.
*/
private String company;
private Sector sector;
@Column(precision=10,scale=2)
private double price;
@Column(precision=10,scale=2)
private double lastPrice;
/**
* A no-arg constructor to comply to both GWT compiler and OpenJPA
* bytecode enhancer.
*/
protected Stock() {
}
public Stock(String symbol, String company, Sector sector, double price) {
super();
this.symbol = symbol;
this.company = company;
this.sector = sector;
this.price = price;
this.lastPrice = price;
}
public String getSymbol() {
return symbol;
}
public String getCompany() {
return company;
}
public Sector getSector() {
return sector;
}
public double getMarketPrice() {
return price;
}
public void setMarketPrice(double newPrice) {
this.lastPrice = this.price;
this.price = newPrice;
}
public double getLastPrice() {
return lastPrice;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((symbol == null) ? 0 : symbol.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Stock other = (Stock) obj;
if (symbol == null) {
if (other.symbol != null)
return false;
} else if (!symbol.equals(other.symbol))
return false;
return true;
}
}

View File

@ -0,0 +1,228 @@
/*
* 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.trader.domain;
import java.io.Serializable;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.MappedSuperclass;
import javax.persistence.OneToOne;
import javax.persistence.Version;
/**
* An abstract root for domain objects in OpenTrader designates a {@link Stock financial instrument}
* that can be traded. An abstract state of a tradable entity is immutable by the application.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
@MappedSuperclass
public abstract class Tradable implements Serializable {
/**
* Primary identity of a traded entity.
* Its value is generated by the persistence provider.
* The application must not set or change this value.
* Hence no setter method is provided.
*/
@Id
@GeneratedValue
private Long id;
/**
* The price at which the underlying instrument be traded.
* Must always be positive.
*/
@Column(precision=10,scale=2)
private double price;
/**
* The volume or discreet number of underlying instrument be traded.
* Must always be positive.
*/
private int volume;
/**
* The underlying instrument.
* Must never be null.
*/
@ManyToOne(cascade={CascadeType.MERGE,CascadeType.DETACH,CascadeType.REFRESH},optional=false)
private Stock stock;
@OneToOne(cascade={CascadeType.MERGE,CascadeType.DETACH}, optional=true, fetch=FetchType.LAZY)
private Trade trade;
/**
* A version identifier.
* Important (and almost mandatory) for any persistent entity to be part of
* simultaneous transaction. The persistence provider uses/updates the
* version to detect concurrent modification of an entity in simultaneous
* transaction.
*/
@Version
private int version;
/**
* A no-arg constructor to comply to both GWT compiler and OpenJPA
* bytecode enhancer.
*/
protected Tradable() {
}
/**
* Real constructor to be used by the concrete derivations.
*
* @param stock the underlying instrument. Must not be null.
* @param price the price. Must be positive.
* @param volume the volume. Must be positive.
*/
protected Tradable(Stock stock, double price, int volume) {
if (stock == null)
throw new IllegalArgumentException("Can not create Tradable with null stock");
if (price <= 0.0)
throw new IllegalArgumentException("Can not create Tradable with non-positive price " + price);
if (volume <= 0)
throw new IllegalArgumentException("Can not create Tradable with non-positive volume " + volume);
this.stock = stock;
this.price = price;
this.volume = volume;
}
/**
* Gets the identifier of this entity.
*
* @return identifier generated by the persistence provider.
*/
public long getId() {
return id;
}
/**
* Gets the underlying instrument.
*
* @return the underlying instrument. Never null.
*/
public Stock getStock() {
return stock;
}
/**
* Gets the price at which the underlying instrument be traded.
*
* @return the price of the underlying instrument for trading. Always greater than zero.
*/
public double getPrice() {
return price;
}
/**
* Gets the volume of the underlying instrument be traded.
*
* @return the volume of the underlying instrument for trading. Always greater than zero.
*/
public int getVolume() {
return volume;
}
public abstract double getGain();
/**
* Affirms if this offer has expired.
*/
public boolean isTraded() {
return trade != null;
}
public void updateStock(Stock updated) {
if (this.stock.equals(updated)) {
this.stock = updated;
} else {
throw new IllegalArgumentException(this + " can not change Stock from " + this.stock
+ " to " + updated);
}
}
public void setTrade(Trade t) {
if (trade != null)
throw new IllegalStateException(this + " has already been traded");
this.trade = t;
}
public Trade getTrade() {
if (trade == null)
throw new IllegalStateException(this + " is not yet traded");
return trade;
}
/**
* The version of the entity. Updated by the persistence provider.
*
* @return the current version of this entity.
*/
public int getVersion() {
return version;
}
/**
* It is important for persistence entity to overwrite the equals()
* method, preferably based only on its primary key attribute(s).
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
/**
* It is important for persistence entity to overwrite the hashCode()
* method, preferably based only on its primary key attribute(s).
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Tradable other = (Tradable) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
public String toString() {
String type = this.getClass().getName();
return type.substring(type.lastIndexOf('.'))+ ":" + id;
}
}

View File

@ -0,0 +1,140 @@
/*
* 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.trader.domain;
import java.io.Serializable;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToOne;
@SuppressWarnings("serial")
@Entity
public class Trade implements Serializable {
/**
* Primary identity of a committed trade.
* Its value is generated by the persistence provider.
* The application must not set or change this value.
* Hence no setter method is provided.
*/
@Id
@GeneratedValue
private Long id;
@Column(precision=10,scale=2)
private double price;
private int volume;
// @ManyToOne(cascade={CascadeType.MERGE,CascadeType.DETACH}, optional=false)
// private Stock stock;
//
// @ManyToOne(cascade={CascadeType.MERGE,CascadeType.DETACH}, optional=false)
// private Trader buyer;
//
// @ManyToOne(cascade={CascadeType.MERGE,CascadeType.DETACH}, optional=false)
// private Trader seller;
@OneToOne(cascade={CascadeType.MERGE,CascadeType.DETACH}, optional=false)
private Ask ask;
@OneToOne(cascade={CascadeType.MERGE,CascadeType.DETACH}, optional=false)
private Bid bid;
protected Trade() {
}
public Trade(Ask a, Bid b) {
if (a == null)
throw new NullPointerException("Can not create Trade with null Ask");
if (b == null)
throw new NullPointerException("Can not create Trade with null Bid");
if (a.getSeller() == null)
throw new NullPointerException("Can not create Trade with null trader for Ask");
if (b.getBuyer() == null)
throw new NullPointerException("Can not create Trade with null trader for Bid");
if (a.getSeller().equals(b.getBuyer()))
throw new IllegalArgumentException("Ask and Bid have same trader " + a.getSeller());
if (!a.getStock().equals(b.getStock())) {
throw new IllegalArgumentException("Stock (ask) " + a.getStock()
+ " does not match Stock (bid) " + b.getStock());
}
// buyer = b.getBuyer();
// seller = a.getSeller();
// stock = a.getStock();
ask = a;
bid = b;
ask.setTrade(this);
bid.setTrade(this);
price = Math.max(a.getPrice(), b.getPrice());
volume = Math.min(a.getVolume(), b.getVolume());
}
public Long getId() {
return id;
}
public Trader getBuyer() {
return bid.getBuyer();
}
public Trader getSeller() {
return ask.getSeller();
}
public Stock getStock() {
return ask.getStock();
}
public double getPrice() {
return price;
}
public int getVolume() {
return volume;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((id == null) ? 0 : id.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Trade other = (Trade) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
return true;
}
}

View File

@ -0,0 +1,87 @@
/*
* 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.trader.domain;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import javax.persistence.Version;
@SuppressWarnings("serial")
@Entity
@Table(name="TRADER",
uniqueConstraints= @UniqueConstraint(columnNames={"NAME"}))
public class Trader implements Serializable {
@Id
private String name;
@Version
private int version;
protected Trader() {
super();
}
public Trader(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int getVersion() {
return version;
}
public String toString() {
return name;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Trader other = (Trader) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}

View File

@ -0,0 +1,10 @@
<HTML>
<BODY>
Persistent Domain Model for OpenTrader.
The domain model is designed to be a operable in the client-side Java runtime environment (JRE) provided by
Google Web Toolkit (GWT). Because <A HREF="http://www.gwtapps.com/doc/html/jre.html">JRE Emulation Library GWT</A>
allows a subset of standard Java library classes, these domain classes are defined to use only that subset so that
they can be translated by GWT Compiler.
</BODY>
</HTML>

View File

@ -0,0 +1,72 @@
/*
* 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.util;
/**
* {@link OpenJPAId} subclass appropriate for long fields.
*
* @author Steve Kim
*/
public final class LongId extends OpenJPAId {
private final long key;
public LongId() {
key = 0;
}
public LongId(Class cls, Long key) {
this(cls, (key == null) ? 0L : key.longValue());
}
public LongId(Class cls, String key) {
this(cls, (key == null) ? 0L : Long.parseLong(key));
}
public LongId(Class cls, long key) {
super(cls);
this.key = key;
}
public LongId(Class cls, long key, boolean subs) {
super(cls, subs);
this.key = key;
}
public long getId() {
return key;
}
public Object getIdObject() {
return key;
}
public String toString() {
return String.valueOf(key);
}
protected int idHash() {
return (int) (key ^ (key >>> 32));
}
protected boolean idEquals(OpenJPAId o) {
return key == ((LongId) o).key;
}
}

View File

@ -0,0 +1,147 @@
/*
* 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.util;
import java.io.Serializable;
import java.util.HashMap;
/**
* Identity class extended by built-in OpenJPA identity objects.
*
* @author Steve Kim
*/
@SuppressWarnings("serial")
public abstract class OpenJPAId
implements Comparable, Serializable {
public static final char TYPE_VALUE_SEP = '-';
// cache the types' generated hash codes
private static HashMap _typeCache = new HashMap();
protected Class type;
protected boolean subs = true;
// type hash is based on the least-derived non-object class so that
// user-given ids with non-exact types match ids with exact types
private transient int _typeHash = 0;
protected OpenJPAId() {
}
protected OpenJPAId(Class type) {
this.type = type;
}
protected OpenJPAId(Class type, boolean subs) {
this.type = type;
this.subs = subs;
}
/**
* Return the persistent class which this id instance represents.
*/
public Class getType() {
return type;
}
/**
* Whether this oid might be for a subclass of the given type.
* Defaults to true.
*/
public boolean hasSubclasses() {
return subs;
}
/**
* Set the exact type of the described instance once it is known.
*/
public void setManagedInstanceType(Class type) {
setManagedInstanceType(type, false);
}
/**
* Set the exact type of the described instance once it is known.
*/
public void setManagedInstanceType(Class type, boolean subs) {
this.type = type;
this.subs = subs;
}
/**
* Return the identity value as an object.
*/
public abstract Object getIdObject();
/**
* Return the id's hash code.
*/
protected abstract int idHash();
/**
* Compare the id to the id of the given instance.
*/
protected abstract boolean idEquals(OpenJPAId other);
/**
* Generate the hash code for this Id. Cache the type's generated hash code
* so that it doesn't have to be generated each time.
*/
public int hashCode() {
if (_typeHash == 0) {
Integer typeHashInt = (Integer) _typeCache.get(type);
if (typeHashInt == null) {
Class base = type;
Class superclass = base.getSuperclass();
while (superclass != null && superclass != Object.class) {
base = base.getSuperclass();
superclass = base.getSuperclass();
}
_typeHash = base.hashCode();
_typeCache.put(type, Integer.valueOf(_typeHash));
} else {
_typeHash = typeHashInt.intValue();
}
}
return _typeHash ^ idHash();
}
public boolean equals(Object o) {
if (o == this)
return true;
if (o == null || getClass() != o.getClass())
return false;
OpenJPAId id = (OpenJPAId) o;
return idEquals(id);
//&& (id.type.isAssignableFrom(type)
// || (subs && type.isAssignableFrom(id.type)));
}
public String toString() {
return type.getName() + TYPE_VALUE_SEP + getIdObject();
}
public int compareTo(Object other) {
if (other == this)
return 0;
if (other == null)
return 1;
return ((Comparable) getIdObject()).compareTo(((OpenJPAId) other).getIdObject ());
}
}

View File

@ -0,0 +1,67 @@
/*
* 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.trader.server;
import java.util.Arrays;
import java.util.List;
/**
* Adapts a server-side exception to a RuntimeException.
*
* @author Pinaki Poddar
*
*/
public class ExceptionAdapter {
static List<String> exceptionTypes = Arrays.asList(
"org.apache.openjpa.persistence.PersistenceException");
RuntimeException translate(Throwable t) {
Throwable cause = searchForKnownButNonTranslatableException(t);
if (cause != null) {
t = cause;
}
RuntimeException e = new RuntimeException(t.getMessage());
e.setStackTrace(t.getStackTrace());
return e;
}
private Throwable searchForKnownButNonTranslatableException(Throwable t) {
if (isAssignable(t.getClass()))
return t;
Throwable nested = t.getCause();
if (nested != null && nested != t) {
return searchForKnownButNonTranslatableException(nested);
}
return null;
}
private boolean isAssignable(Class<?> t) {
if (exceptionTypes.contains(t.getName())) {
return true;
}
if (t.getSuperclass() != Object.class) {
return isAssignable(t.getSuperclass());
}
return false;
}
}

View File

@ -0,0 +1,172 @@
/*
* 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.trader.server;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import org.apache.openjpa.trader.client.TradingServiceAdapter;
import org.apache.openjpa.trader.domain.Ask;
import org.apache.openjpa.trader.domain.Bid;
import org.apache.openjpa.trader.domain.LogStatement;
import org.apache.openjpa.trader.domain.Match;
import org.apache.openjpa.trader.domain.Stock;
import org.apache.openjpa.trader.domain.Tradable;
import org.apache.openjpa.trader.domain.Trade;
import org.apache.openjpa.trader.domain.Trader;
import org.apache.openjpa.trader.service.Exchange;
import org.apache.openjpa.trader.service.MockTradingService;
import org.apache.openjpa.trader.service.TradingService;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
/**
* The server side implementation of the RPC service.
*/
@SuppressWarnings("serial")
public class TradingServiceAdapterImpl extends RemoteServiceServlet implements TradingServiceAdapter {
TradingService _del;
public void init(ServletConfig config) throws ServletException {
super.init(config);
String unit = config.getInitParameter("persistence.unit");
String mock = config.getInitParameter("mock");
_del = ("true".equalsIgnoreCase(mock)) ? new MockTradingService() : new Exchange(unit);
}
public void destroy() {
_del.close();
super.destroy();
}
public Ask ask(Trader trader, Stock stock, int volume, double price) {
try {
return _del.ask(trader, stock, volume, price);
} catch (Throwable e) {
throw translate(e);
}
}
public Bid bid(Trader trader, Stock stock, int volume, double price) {
try {
return _del.bid(trader, stock, volume, price);
} catch (Throwable e) {
throw translate(e);
}
}
public Tradable withdraw(Tradable t) {
try {
return _del.withdraw(t);
} catch (Throwable e) {
throw translate(e);
}
}
public Tradable refresh(Tradable t) {
try {
return _del.refresh(t);
} catch (Throwable e) {
throw translate(e);
}
}
public Stock getStock(String symbol) {
try {
return _del.getStock(symbol);
} catch (Throwable e) {
throw translate(e);
}
}
public List<Stock> getStocks() {
try {
return new ArrayList<Stock>(_del.getStocks());
} catch (Throwable e) {
throw translate(e);
}
}
public List<Trade> getTrades(Timestamp from, Timestamp to) {
try {
return _del.getTrades(from, to);
} catch (Throwable e) {
throw translate(e);
}
}
public List<Trade> getTrades(Trader trader, Boolean boughtOrsold, Timestamp from, Timestamp to) {
try {
return _del.getTrades(trader, boughtOrsold, from, to);
} catch (Throwable e) {
throw translate(e);
}
}
public Trader login(String trader) throws RuntimeException {
try {
return _del.login(trader);
} catch (Throwable e) {
throw translate(e);
}
}
public List<Match> matchAsk(Ask ask) {
try {
return new ArrayList<Match>(_del.matchAsk(ask));
} catch (Throwable e) {
throw translate(e);
}
}
public List<Match> matchBid(Bid bid) {
try {
return new ArrayList<Match>(_del.matchBid(bid));
} catch (Throwable e) {
throw translate(e);
}
}
public Trade trade(Match match) {
try {
return _del.trade(match);
} catch (Throwable e) {
throw translate(e);
}
}
@Override
public List<LogStatement> getLog() {
try {
return _del.getLog();
} catch (Throwable e) {
throw translate(e);
}
}
RuntimeException translate(Throwable t) {
t.printStackTrace();
return new ExceptionAdapter().translate(t);
}
}

View File

@ -0,0 +1,241 @@
/*
* 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.trader.service;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.jdbc.conf.JDBCConfiguration;
import org.apache.openjpa.lib.conf.Configurable;
import org.apache.openjpa.lib.conf.Configuration;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.log.LogFactory;
import org.apache.openjpa.trader.domain.LogStatement;
/**
* Specialized log to consolidate multiple logs into a single one.
* Selects only query related messages.
* <br>
* Designed to capture multiple logs used by different slices. It would have
* been more useful to capture the slice thread that executed the query, but
* that is not possible in all cases as the log statement is emitted from the
* kernel's main thread.
*
* @author Pinaki Poddar
*
*/
public class BufferedLog implements LogFactory, Configurable {
private int _history = 100;
private String _diagCtx;
private Configuration _conf;
static String[] SQL_MARKERS = {"INSERT INTO", "SELECT", "UPDATE", "DELETE"};
static String[] JPQL_MARKERS = {"Executing query: ["};
static List<String> CHANNELS = Arrays.asList(OpenJPAConfiguration.LOG_QUERY, JDBCConfiguration.LOG_SQL);
private static LinkedList<LogStatement> _messageModel;
static {
_messageModel = new LinkedList<LogStatement>();
}
public void setConfiguration(Configuration conf) {
_conf = conf;
}
public void endConfiguration() {
}
public void startConfiguration() {
}
public BufferedLog() {
super();
}
public void setDiagnosticContext(String ctx) {
System.err.println(ctx);
_diagCtx = ctx;
}
public Log getLog(String channel) {
return new ChannelLog(channel);
}
public void setHistory(int i) {
_history = Math.max(i, 1);
}
public int getHistory() {
return _history;
}
String getContext() {
if (_diagCtx != null)
return _diagCtx;
if (_conf == null || isEmpty(_conf.getId()))
return "";
else {
_diagCtx = _conf.getId();
return _conf.getId();
}
}
void addStatement(LogStatement stmt) {
_messageModel.addLast(stmt);
if (_messageModel.size() > _history) {
_messageModel.removeFirst();
}
}
boolean isEmpty(String s) {
return s == null || s.trim().length() == 0;
}
public List<LogStatement> get() {
List<LogStatement> result = new ArrayList<LogStatement>(_messageModel);
_messageModel.clear();
return result;
}
public class ChannelLog implements Log {
final String _channel;
final String _thread;
public ChannelLog(String channel) {
_channel = channel;
_thread = Thread.currentThread().getName();
}
public void error(Object o) {
createLogStatement("ERROR", o, null);
}
public void error(Object o, Throwable t) {
createLogStatement("ERROR", o, t);
}
public void fatal(Object o) {
createLogStatement("FATAL", o, null);
}
public void fatal(Object o, Throwable t) {
createLogStatement("FATAL", o, t);
}
public void info(Object o) {
createLogStatement("INFO", o, null);
}
public void info(Object o, Throwable t) {
createLogStatement("INFO", o, t);
}
public boolean isErrorEnabled() {
return true;
}
public boolean isFatalEnabled() {
return true;
}
public boolean isInfoEnabled() {
return CHANNELS.contains(_channel);
}
public boolean isTraceEnabled() {
return CHANNELS.contains(_channel);
}
public boolean isWarnEnabled() {
return true;
}
public void trace(Object o) {
createLogStatement("TRACE", o, null);
}
public void trace(Object o, Throwable t) {
createLogStatement("TRACE", o, t);
}
public void warn(Object o) {
createLogStatement("WARN", o, null);
}
public void warn(Object o, Throwable t) {
createLogStatement("WARN", o, t);
}
protected void createLogStatement(String level, Object message, Throwable t) {
String msg = message == null ? null : message.toString();
msg = extractQuery(msg);
if (msg == null) {
return;
}
addStatement(new LogStatement(level, getContext(),
_thread, _channel, msg));
if (t != null) {
StringWriter buffer = new StringWriter();
t.printStackTrace(new PrintWriter(buffer));
addStatement(new LogStatement(
level, getContext(),
Thread.currentThread().getName(), _channel,
buffer.toString()));
}
}
public String extractQuery(String msg) {
if (msg == null)
return null;
if ("openjpa.jdbc.SQL".equals(_channel)) {
return getQuery(msg, SQL_MARKERS, true);
} else if ("openjpa.Query".equals(_channel)) {
return getQuery(msg, JPQL_MARKERS, false);
}
return null;
}
private String getQuery(String message, String[] markers, boolean sql) {
int k = -1;
for (int i = 0; i < markers.length; i++) {
k = message.indexOf(markers[i]);
if (k != -1) {
int m = sql ? 0 : markers[i].length();
return message.substring(k+m).trim();
}
}
return null;
}
}
}

View File

@ -0,0 +1,261 @@
/*
* 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.trader.service;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContextType;
import javax.persistence.TypedQuery;
import org.apache.openjpa.lib.log.LogFactory;
import org.apache.openjpa.trader.domain.Ask;
import org.apache.openjpa.trader.domain.Bid;
import org.apache.openjpa.trader.domain.LogStatement;
import org.apache.openjpa.trader.domain.Match;
import org.apache.openjpa.trader.domain.Sector;
import org.apache.openjpa.trader.domain.Stock;
import org.apache.openjpa.trader.domain.Tradable;
import org.apache.openjpa.trader.domain.Trade;
import org.apache.openjpa.trader.domain.Trader;
@SuppressWarnings("serial")
public class Exchange extends PersistenceService implements TradingService {
private BufferedLog log;
public Exchange(String unit) {
this(unit, null);
}
public Exchange(String unit, Map<String,Object> config) {
super(unit, false, PersistenceContextType.TRANSACTION, addLog(config));
LogFactory serverLog = getUnit().getConfiguration().getLogFactory();
try {
log = (BufferedLog)serverLog;
} catch (ClassCastException e) {
System.err.println("Local Log was loaded by " + BufferedLog.class.getClassLoader());
System.err.println("Server Log was loaded by " + serverLog.getClass().getClassLoader());
e.printStackTrace();
}
populate();
new MarketFeed(this).start(60*1000);
}
public Ask ask(Trader trader, Stock stock, int volume, double price) {
EntityManager em = getEntityManager();
begin();
Ask ask = new Ask(trader, stock, price, volume);
em.persist(ask);
commit();
return ask;
}
public Bid bid(Trader trader, Stock stock, int volume, double price) {
EntityManager em = getEntityManager();
begin();
Bid bid = new Bid(trader, stock, price, volume);
em.persist(bid);
commit();
return bid;
}
public List<Match> matchBid(Bid bid) {
EntityManager em = getEntityManager();
begin();
TypedQuery<Match> q = em.createQuery(MATCH_BID, Match.class)
.setParameter("bid", bid);
List<Match> matches = q.getResultList();
commit();
return matches;
}
public List<Match> matchAsk(Ask ask) {
EntityManager em = getEntityManager();
begin();
TypedQuery<Match> q = em.createQuery(MATCH_ASK, Match.class)
.setParameter("ask", ask);
List<Match> matches = q.getResultList();
commit();
return matches;
}
@Override
public Tradable withdraw(Tradable t) {
if (t.isTraded()) {
throw new IllegalStateException("Can not widthdraw " + t + ". It has already been traded");
}
EntityManager em = getEntityManager();
begin();
em.createQuery("delete from " + (t instanceof Ask ? "Ask" : "Bid") + " t where t.id=:id")
.setParameter("id", t.getId())
.executeUpdate();
commit();
return t;
}
/**
* Refresh may fail for various reasons.
* The tradable might have been traded or withdrawn.
*/
@Override
public Tradable refresh(Tradable t) {
EntityManager em = getEntityManager();
begin();
t = em.find(t.getClass(), t.getId());
commit();
return t;
}
public Trade trade(Match match) {
EntityManager em = getEntityManager();
begin();
Ask ask = em.merge(match.getAsk());
Bid bid = em.merge(match.getBid());
Trade trade = new Trade(ask, bid);
em.persist(trade);
commit();
return trade;
}
public Trader login(String traderName) {
EntityManager em = getEntityManager();
begin();
Trader trader = em.find(Trader.class, traderName);
if (trader == null) {
trader = new Trader(traderName);
em.persist(trader);
}
commit();
return trader;
}
public Stock getStock(String symbol) {
EntityManager em = getEntityManager();
begin();
Stock stock = em.find(Stock.class, symbol);
em.refresh(stock);
commit();
return stock;
}
public List<Trade> getTrades(Timestamp from, Timestamp to) {
EntityManager em = getEntityManager();
begin();
List<Trade> result = em.createQuery(QUERY_TRADE_BY_PERIOD, Trade.class)
.setParameter("from", from.getNanos())
.setParameter("to", to.getNanos())
.getResultList();
commit();
return result;
}
public List<Trade> getTrades(Trader trader, Boolean bought, Timestamp from, Timestamp to) {
EntityManager em = getEntityManager();
begin();
StringBuilder jpql = new StringBuilder(QUERY_TRADE_BY_PERIOD);
if (Boolean.TRUE.equals(bought)) {
jpql.append(" AND t.buyer = : buyer");
} else if (Boolean.FALSE.equals(bought)) {
jpql.append(" AND t.seller = : seller");
}
TypedQuery<Trade> q = em.createQuery(jpql.toString(), Trade.class);
if (Boolean.TRUE.equals(bought)) {
q.setParameter("buyer", trader);
} else if (Boolean.FALSE.equals(bought)) {
q.setParameter("seller", trader);
}
q.setParameter("from", from.getNanos())
.setParameter("to", to.getNanos());
List<Trade> result = q.getResultList();
commit();
return result;
}
public void populate() {
Object[][] data = {
new Object[]{"IBM", Sector.INFRASTRUCTURE, 140.03},
new Object[]{"ORCL", Sector.INFRASTRUCTURE, 20.04},
new Object[]{"MSFT", Sector.INFRASTRUCTURE, 32.0},
new Object[]{"Bayer", Sector.HEALTHCARE, 120.45},
new Object[]{"SMNS", Sector.HEALTHCARE, 34.98},
new Object[]{"CSCO", Sector.INFRASTRUCTURE, 23.45},
new Object[]{"GS", Sector.FINACE, 120.09},
new Object[]{"IFN", Sector.FINACE, 265.87},
};
EntityManager em = getEntityManager();
begin();
List<Stock> stocks = em.createQuery(GET_ALL_STOCKS, Stock.class).getResultList();
if (stocks.isEmpty()) {
for (int i = 0; i < data.length; i++) {
Object[] d = data[i];
Stock stock = new Stock((String)d[0], (String)d[0], (Sector)d[1], (Double)d[2]);
em.persist(stock);
}
for (int i = 0; i < 4; i++) {
Trader trader = new Trader("Trader-"+i);
em.persist(trader);
}
stocks = em.createQuery(GET_ALL_STOCKS, Stock.class).getResultList();
}
commit();
}
public List<Stock> getStocks() {
EntityManager em = getEntityManager();
begin();
List<Stock> stocks = em.createQuery(GET_ALL_STOCKS, Stock.class).getResultList();
commit();
return stocks;
}
public List<LogStatement> getLog() {
if (log == null) {
return new ArrayList<LogStatement>();
}
return log.get();
}
@Override
public void close() {
super.close();
}
static Map<String,Object> addLog(Map<String,Object> config) {
if (config == null) {
config = new HashMap<String, Object>();
}
config.put("openjpa.Log", BufferedLog.class.getName());
return config;
}
}

View File

@ -0,0 +1,65 @@
/*
* 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.trader.service;
import java.util.List;
import java.util.Random;
import java.util.Timer;
import java.util.TimerTask;
import javax.persistence.EntityManager;
import org.apache.openjpa.trader.domain.Stock;
public class MarketFeed extends TimerTask {
/**
* A query to find symbols of all stocks.
*/
String GET_ALL_STOCKS = "select s from Stock s";
private static final Random rng = new Random(System.currentTimeMillis());
private static final int MAX_CHANGE = 10;
private final PersistenceService _service;
MarketFeed(PersistenceService service) {
super();
_service = service;
}
void start(long period) {
new Timer(true).schedule(this, 0, period);
}
@Override
public void run() {
EntityManager em = _service.newEntityManager();
em.getTransaction().begin();
List<Stock> stocks = em.createQuery(GET_ALL_STOCKS, Stock.class).getResultList();
int n = stocks.size();
for (int i = 0; i < n; i++) {
if (rng.nextDouble() < 0.25) {
Stock stock = stocks.get(i);
double oldPrice = stock.getMarketPrice();
double delta = (rng.nextDouble() - 0.5) * MAX_CHANGE;
double newPrice = Math.max(oldPrice + delta, 0.01);
stock.setMarketPrice(newPrice);
}
}
em.getTransaction().commit();
}
}

View File

@ -0,0 +1,175 @@
/*
* 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.trader.service;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import org.apache.openjpa.trader.domain.Ask;
import org.apache.openjpa.trader.domain.Bid;
import org.apache.openjpa.trader.domain.LogStatement;
import org.apache.openjpa.trader.domain.Match;
import org.apache.openjpa.trader.domain.Sector;
import org.apache.openjpa.trader.domain.Stock;
import org.apache.openjpa.trader.domain.Tradable;
import org.apache.openjpa.trader.domain.Trade;
import org.apache.openjpa.trader.domain.Trader;
public class MockTradingService implements TradingService {
List<Ask> _asks = new ArrayList<Ask>();
List<Bid> _bids = new ArrayList<Bid>();
List<Trade> _trades = new ArrayList<Trade>();
List<Stock> _stocks = new ArrayList<Stock>();
List<Trader> _traders = new ArrayList<Trader>();
List<LogStatement> _logs = new ArrayList<LogStatement>();
int counter = 0;
private static Random rng = new Random(System.currentTimeMillis());
public MockTradingService() {
Sector[] sectors = Sector.values();
for (int i = 0; i < 10; i++) {
Stock stock = new Stock("Stock-"+i, "Company-"+i, sectors[rng.nextInt(sectors.length)],
10*rng.nextDouble());
_stocks.add(stock);
}
}
@Override
public Ask ask(Trader trader, Stock stock, int volume, double price) {
Ask ask = new Ask(trader, stock, price, volume);
_asks.add(ask);
log("Added " + ask + " " + counter++);
return ask;
}
@Override
public Bid bid(Trader trader, Stock stock, int volume, double price) {
Bid bid = new Bid(trader, stock, price, volume);
_bids.add(bid);
log("Added new " + bid + " " + counter++);
return bid;
}
@Override
public List<LogStatement> getLog() {
int from = Math.max(_logs.size()-5, 0);
List<LogStatement> result = new ArrayList<LogStatement>();
for (int i = from; i < _logs.size(); i++)
result.add(_logs.get(i));
return result;
}
@Override
public Stock getStock(String symbol) {
for (Stock s : _stocks) {
if (s.getSymbol().equals(symbol))
return s;
}
log("No Stock " + symbol);
return null;
}
@Override
public List<Stock> getStocks() {
for (Stock s : _stocks) {
double delta = 10*(2*rng.nextDouble()-1)/100;
s.setMarketPrice(s.getMarketPrice() + s.getMarketPrice()*delta);
}
return _stocks;
}
@Override
public List<Trade> getTrades(Timestamp from, Timestamp to) {
return null;
}
@Override
public List<Trade> getTrades(Trader trader, Boolean boughtOrsold, Timestamp from, Timestamp to) {
return null;
}
@Override
public Trader login(String trader) {
for (Trader t : _traders) {
if (t.getName().equals(trader))
return t;
}
Trader t = new Trader(trader);
_traders.add(t);
return t;
}
@Override
public List<Match> matchAsk(Ask ask) {
List<Match> result = new ArrayList<Match>();
for (Bid bid : _bids) {
if (matches(ask, bid)) {
result.add(new Match(ask, bid));
}
}
return result;
}
@Override
public List<Match> matchBid(Bid bid) {
List<Match> result = new ArrayList<Match>();
for (Ask ask : _asks) {
if (matches(ask, bid)) {
result.add(new Match(ask, bid));
}
}
return result;
}
public Tradable refresh(Tradable t) {
return t;
}
@Override
public Trade trade(Match match) {
Trade trade = new Trade(match.getAsk(), match.getBid());
return trade;
}
private boolean matches(Ask ask, Bid bid) {
return ((bid.getStock().getSymbol().equals(ask.getStock().getSymbol()))
&& (!bid.getBuyer().equals(ask.getSeller()))
&& (bid.getPrice() >= ask.getPrice())
&& (bid.getVolume()) <= ask.getVolume());
}
private void log(String s) {
_logs.add(new LogStatement("INFO", "Context", "Thread", "Channel", s));
System.err.println("server log:" + s );
}
@Override
public Tradable withdraw(Tradable t) {
// t.expire();
return t;
}
@Override
public void close() {
}
}

View File

@ -0,0 +1,216 @@
/*
* 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.trader.service;
import java.io.Serializable;
import java.util.Map;
import java.util.Observable;
import java.util.concurrent.locks.ReentrantLock;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.PersistenceContextType;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory;
import org.apache.openjpa.persistence.OpenJPAPersistence;
/**
* An abstract utility for JPA based service.
* This thin wrapper over a {@link EntityManagerFactory Persistence Unit} maintains
* <LI>per-thread persistence context
* <LI>relinquishes direct transaction control under a managed environment
*
* @see #getEntityManager()
* @see #newEntityManager()
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public abstract class PersistenceService extends Observable implements Serializable {
private final OpenJPAEntityManagerFactory emf;
private final String unitName;
private final boolean isManaged;
private final PersistenceContextType scope;
private ThreadLocal<EntityManager> thread = new ThreadLocal<EntityManager>();
private ReentrantLock lock = new ReentrantLock();
protected PersistenceService(String unit) {
this(unit, false, PersistenceContextType.EXTENDED, null);
}
protected PersistenceService(String unit, boolean managed, PersistenceContextType scope,
Map<String,Object> config) {
this.emf = OpenJPAPersistence.cast(Persistence.createEntityManagerFactory(unit, config));
this.unitName = unit;
this.isManaged = managed;
this.scope = scope;
}
public final OpenJPAEntityManagerFactory getUnit() {
return emf;
}
public final String getUnitName() {
return unitName;
}
public final boolean isManaged() {
return isManaged;
}
public final PersistenceContextType getContextType() {
return scope;
}
/**
* Gets an entity manager associated with the current thread. If the
* current thread is not associated with any entity manager or the
* associated entity manager has been closed, creates a new entity manager
* and associates with the current thread.
*
* @return an entity manager associated with the current thread.
*/
protected EntityManager getEntityManager() {
try {
lock.lock();
EntityManager em = thread.get();
if (em == null || !em.isOpen()) {
em = emf.createEntityManager();
thread.set(em);
}
return em;
} finally {
lock.unlock();
}
}
/**
* Creates a new entity manager. The entity manager is not associated with
* the current thread.
*/
protected EntityManager newEntityManager() {
return emf.createEntityManager();
}
/**
* Begins a transaction on the current thread. If the thread is associated
* with a persistence context, then a transaction is started if necessary.
* If the thread is not associated with a persistence context, then a new
* context is created, associated with the thread, new transaction is
* started.
*
* @see #getEntityManager()
*/
protected EntityManager begin() {
try {
lock.lock();
EntityManager em = getEntityManager();
if (isManaged) {
em.joinTransaction();
} else {
if (!em.getTransaction().isActive()) {
em.getTransaction().begin();
}
}
return em;
} finally {
lock.unlock();
}
}
/**
* Commits a transaction on the current thread.
*/
protected void commit() {
try {
lock.lock();
EntityManager em = getEntityManager();
if (isManaged) {
em.flush();
} else {
assertActive();
em.getTransaction().commit();
}
if (scope == PersistenceContextType.TRANSACTION) {
em.clear();
}
} finally {
lock.unlock();
}
}
/**
* Rolls back a transaction on the current thread.
*/
protected void rollback() {
try {
lock.lock();
EntityManager em = getEntityManager();
if (isManaged) {
} else {
em.getTransaction().rollback();
}
if (scope == PersistenceContextType.TRANSACTION) {
em.clear();
}
} finally {
lock.unlock();
}
}
public void close() {
try {
EntityManager em = thread.get();
if (em != null && em.getTransaction().isActive()) {
em.getTransaction().rollback();
em.close();
}
thread.set(null);
emf.close();
} finally {
}
}
/**
* Assert current thread is associated with an active transaction.
*/
protected void assertActive() {
EntityManager em = thread.get();
String thread = Thread.currentThread().getName();
assertTrue("No persistent context is associated with " + thread, em != null);
assertTrue("Persistent context " + em + " associated with " + thread + " has been closed", em.isOpen());
if (!isManaged) {
assertTrue("Persistent context " + em + " associated with " + thread + " has no active transaction",
em.getTransaction().isActive());
}
}
protected void assertTrue(String s, boolean p) {
if (!p) {
System.err.println(s);
throw new RuntimeException(s);
}
}
}

View File

@ -0,0 +1,38 @@
/*
* 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.trader.service;
public class TradeEvent {
enum Type {LOGIN, ASK, BID, WITHDRAW, REFRESH, TRADE, MATCH,
STOCK_LOOKUP, QUERY_TRADE, MARKET_FEED};
public final Type type;
public final long time;
public final String description;
public TradeEvent(Type type, long elapsedTime) {
this(type, elapsedTime, "");
}
public TradeEvent(Type type, long elapsedTime, String txt) {
this.type = type;
this.time = elapsedTime;
this.description = txt;
}
}

View File

@ -0,0 +1,164 @@
/*
* 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.trader.service;
import java.sql.Timestamp;
import java.util.List;
import org.apache.openjpa.trader.domain.Ask;
import org.apache.openjpa.trader.domain.Bid;
import org.apache.openjpa.trader.domain.LogStatement;
import org.apache.openjpa.trader.domain.Match;
import org.apache.openjpa.trader.domain.Stock;
import org.apache.openjpa.trader.domain.Tradable;
import org.apache.openjpa.trader.domain.Trade;
import org.apache.openjpa.trader.domain.Trader;
/**
* A service to place offer to {@link Bid buy} and {@link Ask sell} stocks,
* matches asks to bids and registers trades.
*
* @author Pinaki Poddar
*
*/
public interface TradingService {
/**
* A query to find symbols of all stocks.
*/
String GET_ALL_STOCKS = "select s from Stock s";
/**
* A query to match asks to a given bid.
*/
String MATCH_BID = "select new Match(a,b) from Ask a, Bid b "
+ "where b = :bid and a.stock.symbol = b.stock.symbol "
+ "and a.price <= b.price and a.volume >= b.volume "
+ "and NOT(a.seller = b.buyer) and a.trade is NULL and b.trade is NULL";
// + "order by a.price ASC";
/**
* A query to match bids of a given ask.
*/
String MATCH_ASK = "select new Match(a,b) from Ask a, Bid b "
+ "where a = :ask and a.stock.symbol = b.stock.symbol "
+ "and a.price <= b.price and a.volume >= b.volume "
+ "and NOT(a.seller = b.buyer) and a.trade is NULL and b.trade is NULL";
// + "order by b.price DESC";
/**
* A query to find a trader by his/her name.
*/
String QUERY_TRADER_BY_NAME = "select t from Trader t where t.name=:name";
/**
* A query to find all trades in a given period.
*/
String QUERY_TRADE_BY_PERIOD = "select t from Trade t where t.id between (:from, :to)";
public static final String DEFAULT_UNIT_NAME = "exchange";
/**
* Gets the list of stocks registered with this service.
*/
List<Stock> getStocks();
/**
* Logs in a trader of given name.
*
* @param trader
* @return
*/
Trader login(String trader);
/**
* The given Trader asks (offers to sell) the given stock at given price.
* @param trader
* @param stock
* @param volume
* @param price
* @return
*/
Ask ask(Trader trader, Stock stock, int volume, double price);
/**
* The given Trader bids (offers to buy) the given stock at given price.
* @param trader
* @param stock
* @param volume
* @param price
* @return
*/
Bid bid(Trader trader, Stock stock, int volume, double price);
/**
* Matches existing asks to the given bid.
* @param bid the bid to be matched
* @return possible (uncommitted) trades matching the bids
*/
List<Match> matchBid(Bid bid);
/**
* Matches existing bids to the given ask.
* @param ask the ask to be matched with the bids
* @return possible (uncommitted) trades matching the given ask
*/
List<Match> matchAsk(Ask ask);
Tradable withdraw(Tradable t);
Tradable refresh(Tradable t);
void close();
/**
* Registers the given trade.
* @param trade
*/
Trade trade(Match match);
/**
* Gets the trades executed between the given time periods.
* @param from
* @param to
* @return
*/
List<Trade> getTrades(Timestamp from, Timestamp to);
/**
* Gets the trades executed by the given trader between the given time periods.
* @param from
* @param to
* @return
*/
List<Trade> getTrades(Trader trader, Boolean boughtOrsold, Timestamp from, Timestamp to);
/**
* Gets the current value of the stock given its symbol.
* @param symbol
* @return
*/
Stock getStock(String symbol);
/**
* Gets the statements logged since the last call.
* @return
*/
List<LogStatement> getLog();
}

View File

@ -0,0 +1,56 @@
/*
* 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.trader.service.slice;
import java.util.List;
import org.apache.openjpa.slice.DistributionPolicy;
import org.apache.openjpa.trader.domain.Stock;
import org.apache.openjpa.trader.domain.Tradable;
import org.apache.openjpa.trader.domain.Trader;
/**
* Distributes each persistent domain instances of OpenTrader model into specific slice
* based on the sector.
*
* @author Pinaki Poddar
*
*/
public class SectorDistributionPolicy implements DistributionPolicy {
/**
* This distribution policy determines the sector of the stock and
* picks the slice of the given list of slices at ordinal index of the
* enumerated Sector.
*/
public String distribute(Object pc, List<String> slices, Object context) {
Stock stock = null;
if (pc instanceof Tradable) {
stock = ((Tradable)pc).getStock();
} else if (pc instanceof Stock) {
stock = (Stock)pc;
} else if (pc instanceof Trader) {
throw new IllegalArgumentException("Trader should have been replicated");
}
// if (stock == null) {
// throw new IllegalStateException(pc + "(" + pc.getClass() + ") is not associated with a Stock");
// }
return stock != null ? slices.get(stock.getSector().ordinal()) : null;
}
}

View File

@ -0,0 +1,337 @@
/**
* The stylesheet for OpenTrader sample application.
*
**/
/**
* GWT Widgets have pre-defined style names. These pre-defined style names
* can be redecorated for a specific application.
*
**/
.gwt-Label {
font-family: "Palatino Linotype";
}
.gwt-PopupPanel {
font-family: "Palatino Linotype";
border: 10px;
}
.gwt-PopupPanel .popupContent {
font-family: "Palatino Linotype";
border: 50px;
}
/**
* LoginDialog is a specialized PopupPanel that organizes its content in a FlexTable.
* The CSS styles apply to the table cells.
**/
.login {
color:white;
font-weight:bold;
border: 10px;
background-color:#005B9A;
cell-spacing:10px;
cell-padding:10px;
}
/**
* A special style apply to the caption of the login dialog -- which is the first row
* of the FlexTable that the LoginDialog wraps in a PopupPanel.
**/
.login-caption {
font-family: "Palatino Linotype";
font-size:20pt;
font-weight:bold;
color: white;
}
/** ---------------------------------------------------------------------------
* CSS Styles used in ScrollableTable.
* ---------------------------------------------------------------------------
**/
.cbg-RP {
background-color:#005B9A;
border-color: #005B9A;
}
.table-caption {
background-color:#005B9A;
font-size:1.1em;
font-weight:bold;
color:#FFFFFF;
font-family: "Palatino Linotype";
}
.column-header {
background-color:#74C2E1;
font-weight:bold;
font-family: "Palatino Linotype";
color:#838984;
}
.row-even {
background-color:#FFFFFF;
}
.row-odd {
background-color:#CCCCCC;
}
.progressMonitor-caption {
background-color:#005B9A;
font-family: "Palatino Linotype";
font-size:14pt;
font-weight:bold;
color: white;
}
.openjpa-MessageBox {
align:center;
background-color:#EEEEEE;
width:300px;
height:150px;
}
.header {
font-size: 20pt;
font-weight: bold;
color: #005B9A;
font-family: "Palatino Linotype";
}
.hello {
float:right;
text-align:right;
margin-right:1em;
}
.error-message {
color:red;
font-weight:plain;
word-wrap:word-spacing;
}
.table-header {
color:blue;
background-color:green;
font-family: "Palatino Linotype";
}
#LogoPanel {
position: absolute;
top:0px;
left:10px;
width: 800px;
height: 40px;
border-style:double;
font-size: 20pt;
font-weight: bold;
color: #0000FF;
/* vertical-align:-25%; */
}
/** Panel on top-right corner displays current Trader details **/
#TraderPanel {
position: absolute;
top:10px;
right:10px;
width: 300px;
height: 30px;
border-style:double;
}
/** Stock Panel displays the current stock prices that are being regularly refreshed **/
#StockPanel {
position: absolute;
top:60px;
left:100px;
width: 400px;
height:800px;
border-style:double;
}
/** Ask Panel controls placing an offer to sell a stock **/
#AskPanel {
position: absolute;
top:60px;
left:500px;
width: 400px;
height: 200px;
border-style:double;
}
/** Bid Panel controls placing an offer to buy a stock **/
#BidPanel {
position: absolute;
top:60px;
left:1000px;
width: 400px;
height:200px;
border-style:double;
}
/** Trade Panel controls commit to buy/sell a stock **/
#TradePanel {
position: absolute;
top:300px;
left:500px;
width: 900px;
height:300px;
border-style:double;
}
#TradeHistoryPanel {
position: absolute;
top:60px;
left:1440px;
width: 460px;
height:540px;
border-style:double;
}
#ServerPanel {
position: absolute;
top:620px;
left:500px;
width: 1400px;
height:300px;
border-style:double;
}
#FooterPanel {
position: absolute;
top:1000px;
left:10px;
width: 1400px;
height:40px;
border-style:double;
}
/* stock list flex table */
.StockTable {
border: 1px solid silver;
padding: 2px;
margin-bottom:6px;
}
/* stock list header row */
.StockTableHeader {
background-color: #2062B8;
color: white;
font-weight: bold;
text-align:center;
}
.NumericColumn {
text-align: right;
width:8em;
}
.positive {
color: green;
}
.negative {
color: red;
}
.sql-select {
color:#005B9A;
font-family: "Courier New";
font-size:small;
}
.sql-insert {
color:#31B96E;
font-family: "Courier New";
font-size:small;
font-weight:bold;
}
.sql-update {
color:#C73B0B;
font-family: "Courier New";
font-size:small;
}
.sql-delete {
color:#FF0000;
font-family: "Courier New";
font-size:small;
}
h2 {
font-size: 2em;
font-weight: bold;
color: #777777;
/** margin: 40px 0px 70px; **/
text-align: left;
vertical-align:top;
}
.sendButton {
display: block;
font-size: 16pt;
}
/** Most GWT widgets already have a style name defined */
.ErrorDialog {
top:400px;
left:400px;
width: 800px;
}
.gwt-DialogBox .Caption {
color:red;
font-weight:bold;
font-size:16pt;
}
.gwt-DialogBox .dialogContent {
margin : 2px;
}
.dialogVPanel {
width: 800px;
margin: 5px;
}
.serverResponseLabelError {
color: red;
}
/** Set ids using widget.getElement().setId("idOfElement") */
#closeButton {
margin: 15px 6px 6px;
}
.TradeOrderWindow {
font-size:0.8em;
font-weight:bold;
color:#FFFFFF;
background-color:#0191C8;
width:40px;
height:25px;
margin: 4 10 4 10;
}
body, table td, select {
font-family: "Palatino Linotype";
font-size:small;
}
.gwt-Button {
margin: 0;
background-color:#74C2E1;
padding: 3px 5px;
text-decoration: none;
font-family: "Palatino Linotype";
font-size: small;
cursor: pointer;
cursor: hand;
background: url("images/hborder.png") repeat-x 0px -27px;
border: 1px outset #333;
}
.gwt-Anchor {
color:#FFFFFF;
}