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) + "
";
+ 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(""));
+
+ 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("
"));
+ }
+ 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}.
+ *
+ * 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 {
+ 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));
+ }
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/OpenTradeImageBundle.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/OpenTradeImageBundle.java
new file mode 100644
index 000000000..02aa0364c
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/OpenTradeImageBundle.java
@@ -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.
+ *
+ * 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();
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/OpenTrader.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/OpenTrader.java
new file mode 100644
index 000000000..fe30e95b3
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/OpenTrader.java
@@ -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.
+ *
+ *
Initialization:
+ * 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.
+ *
+ * Operation:
+ * 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.
+ *
+ * Event Management:
+ * 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 service events} to represent
+ * the business functions such as a new Ask or Bid request is placed, or a Trade
+ * committed etc.
+ *
+ * Session State Management: 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 common,
+ * shared 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 soldTradePanel; // displays committed trades
+ private ScrollableTable 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 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 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(""));
+ west.add(decorate(soldTradePanel));
+ west.add(new HTML("
"));
+ west.add(decorate(boughtTradePanel));
+
+ FlowPanel center = new FlowPanel();
+ center.setSize(centerWidth+"px", centerHeight+"px");
+ center.add(decorate(orderPanel));
+ center.add(new HTML("
"));
+ center.add(decorate(tradePanel));
+ center.add(new HTML("
"));
+ 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.
+ *
+ * GWT framework supports this patter out-of-the-box. This application reuses the
+ * framework for a set of {@link ServiceEvent service events}.
+ */
+ public void registerHandler(Type 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);
+ }
+
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ServerLogPanel.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ServerLogPanel.java
new file mode 100644
index 000000000..7eb9e2387
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ServerLogPanel.java
@@ -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.
+ *
+ * CSS styles are
+ * sql-insert
+ * sql-update
+ * sql-select
+ * sql-delete
+ *
+ * @author Pinaki Poddar
+ *
+ */
+public class ServerLogPanel extends ScrollableTable
+ 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() {
+ public Widget render(LogStatement log) {
+ return new Label(log.getContext());
+ }
+ });
+ setRenderer(1, new GridCellRenderer() {
+ 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> {
+ public void onFailure(Throwable caught) {
+ session.handleError(caught);
+ }
+
+ public void onSuccess(List messages) {
+ if (messages == null)
+ return;
+ int N = messages.size();
+ for (int i = 0; i < N; i++) {
+ insert(messages.get(i));
+ }
+ }
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/TradeHistoryPanel.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/TradeHistoryPanel.java
new file mode 100644
index 000000000..65b4730d7
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/TradeHistoryPanel.java
@@ -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
+ 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() {
+ public Widget render(Trade model) {
+ return new Label(model.getStock().getSymbol());
+ }
+ });
+
+ // Price of the trade
+ setRenderer(1, new GridCellRenderer() {
+ public Widget render(Trade t) {
+ return FormatUtil.formatPrice(t.getPrice());
+ }
+ });
+
+ // Volume of the trade
+ setRenderer(2, new GridCellRenderer() {
+ public Widget render(Trade t) {
+ return FormatUtil.formatVolume(t.getVolume());
+ }
+ });
+
+ // Counter Party
+ setRenderer(3, new GridCellRenderer() {
+ 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());
+ }
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/TradeOrderWindow.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/TradeOrderWindow.java
new file mode 100644
index 000000000..ae7b447a8
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/TradeOrderWindow.java
@@ -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.
+ *
+ * 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 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 {
+ 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 {
+ public void onSuccess(Bid result) {
+ session.fireEvent(new ServiceEvent.TradableAdded(result));
+ }
+
+ public void onFailure(Throwable caught) {
+ session.handleError(caught);
+ }
+
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/TradingServiceAdapter.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/TradingServiceAdapter.java
new file mode 100644
index 000000000..039119b22
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/TradingServiceAdapter.java
@@ -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.
+ *
+ * This is a delegating interface to the original {@link TradingService} interface. This delegation
+ * pattern serves several purposes
+ * 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 javax.persistence.PersistenceException
. To propagate
+ * these exceptions to the client, these exceptions need to be translated for serialization by
+ * GWT compiler and will bring in heavier dependency.
+ *
+ * On the other hand, GWT will not propagate a non-translatable exception to the client,
+ * thereby making develop-debug cycles harder.
+ * b) GWT requires the service interface to extend GWT-defined
+ * com.google.gwt.user.client.rpc.RemoteService
. But the discipline used for this
+ * application dictates that the service interface be independent of either
+ * how it is implemented or how it will be accessed. That is why original {@link TradingService}
+ * definition neither depends on javax.persistence.*
nor on com.google.gwt.*
.
+ *
+ * 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.
+ *
+ *
+ * Any type appearing in this interface must be serializable per GWT requirement.
+ */
+
+/**
+ * This @RemoteServiceRelativePath
annotation defines the relative URL of the
+ * deployed servlet. It appears in web.xml
as:
+ *
+ * <servlet-mapping>
+ * <servlet-name>opentrader</servlet-name>
+ * <url-pattern>/opentrader/trade</url-pattern>
+ * </servlet-mapping>
+ *
+ *
+ * The servlet name matches the name used in module descriptor OpenTrader.gwt.xml
as
+ *
+ * <module rename-to='opentrader'>
+ *
+ */
+@RemoteServiceRelativePath("trade")
+public interface TradingServiceAdapter extends RemoteService {
+ Trader login(String name)
+ throws RuntimeException;
+ List 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 matchBid(Bid bid)
+ throws RuntimeException;
+ List matchAsk(Ask ask)
+ throws RuntimeException;
+ Trade trade(Match match)
+ throws RuntimeException;
+ List getTrades(Timestamp from, Timestamp to)
+ throws RuntimeException;
+ List getTrades(Trader trader, Boolean boughtOrsold, Timestamp from, Timestamp to)
+ throws RuntimeException;
+ Stock getStock(String symbol)
+ throws RuntimeException;
+ List getLog()
+ throws RuntimeException;
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/TradingServiceAdapterAsync.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/TradingServiceAdapterAsync.java
new file mode 100644
index 000000000..e929c3bed
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/TradingServiceAdapterAsync.java
@@ -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 callback);
+
+ void bid(Trader trader, Stock stock, int volume, double price, AsyncCallback callback);
+
+ void getStock(String symbol, AsyncCallback callback);
+
+ void getStocks(AsyncCallback> callback);
+
+ void getTrades(Timestamp from, Timestamp to, AsyncCallback> callback);
+
+ void getTrades(Trader trader, Boolean boughtOrsold, Timestamp from, Timestamp to,
+ AsyncCallback> callback);
+
+ void login(String trader, AsyncCallback callback);
+
+ void matchAsk(Ask ask, AsyncCallback> callback);
+
+ void matchBid(Bid bid, AsyncCallback> callback);
+
+ void trade(Match match, AsyncCallback callback);
+
+ void getLog(AsyncCallback> callback);
+
+ void withdraw(Tradable t, AsyncCallback callback);
+
+ void refresh(Tradable t, AsyncCallback callback);
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/TradingWindow.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/TradingWindow.java
new file mode 100644
index 000000000..4235e5df9
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/TradingWindow.java
@@ -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.
+ *
+ * This widget demonstrates combination of both read-only and updatable visual elements
+ * as well as active widgets such as a button.
+ *
+ * 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,
+ *
+ * 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
+ 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() {
+ public Widget render(Tradable model) {
+ return new Label(model.getStock().getSymbol());
+ }
+ });
+
+ // Market Price as changing Label
+ setRenderer(1, new GridCellRenderer() {
+ public Widget render(Tradable model) {
+ return FormatUtil.formatPrice(model.getStock().getMarketPrice());
+ }
+ });
+
+ // Ask/Bid Price as Label
+ setRenderer(2, new GridCellRenderer() {
+ public Widget render(Tradable model) {
+ return FormatUtil.formatPrice(model.getPrice());
+ }
+ });
+
+ // Ask/Bid Volume as Label
+ setRenderer(3, new GridCellRenderer() {
+ public Widget render(Tradable model) {
+ return FormatUtil.formatVolume(model.getVolume());
+ }
+ });
+
+ // Gain or loss
+ setRenderer(4, new GridCellRenderer() {
+ public Widget render(Tradable t) {
+ return FormatUtil.formatChange(t.getGain());
+ }
+ });
+
+ // Buy/Sell Button
+ setRenderer(5, new GridCellRenderer() {
+ 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() {
+ 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, 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>, ClickHandler {
+ final Tradable tradable;
+
+ public MatchCallback(Tradable tradable) {
+ super();
+ this.tradable = tradable;
+ }
+
+ public void onFailure(Throwable caught) {
+ session.handleError(caught);
+ }
+
+ public void onSuccess(List 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 {
+ 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);
+ }
+ }
+
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/event/ServiceEvent.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/event/ServiceEvent.java
new file mode 100644
index 000000000..4a3af3f07
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/event/ServiceEvent.java
@@ -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
+ * @param
+ */
+public abstract class ServiceEvent extends GwtEvent {
+ private final T payload;
+
+ protected ServiceEvent(T data) {
+ payload = data;
+ }
+
+ public final T getPayload() {
+ return payload;
+ }
+
+ public static class TradableAdded extends ServiceEvent {
+ public static Type TYPE = new Type();
+
+ 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 getAssociatedType() {
+ return TYPE;
+ }
+ }
+
+ public static class TradableRemoved extends ServiceEvent {
+ public static Type TYPE = new Type();
+
+ 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 getAssociatedType() {
+ return TYPE;
+ }
+ }
+
+ public static class StockUpdated extends ServiceEvent {
+ public static Type TYPE = new Type();
+
+ public StockUpdated(Stock stock) {
+ super(stock);
+
+ }
+
+ @Override
+ protected void dispatch(UpdateStockHandler handler) {
+ handler.onStockUpdated(this);
+ }
+
+ @Override
+ public com.google.gwt.event.shared.GwtEvent.Type getAssociatedType() {
+ return TYPE;
+ }
+ }
+
+ public static class TradeCommitted extends ServiceEvent {
+ public static Type TYPE = new Type();
+
+ 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 getAssociatedType() {
+ return TYPE;
+ }
+ }
+
+
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/event/ServiceEventHandler.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/event/ServiceEventHandler.java
new file mode 100644
index 000000000..b6385d655
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/event/ServiceEventHandler.java
@@ -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);
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/ErrorDialog.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/ErrorDialog.java
new file mode 100644
index 000000000..dbf3d4b13
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/ErrorDialog.java
@@ -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.
+ *
+ * CSS Style names
+ * errorDialog-caption:
+ * 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;
+ }
+
+}
\ No newline at end of file
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/FadeEffect.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/FadeEffect.java
new file mode 100644
index 000000000..26b3e9118
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/FadeEffect.java
@@ -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;
+ }
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/GridCellRenderer.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/GridCellRenderer.java
new file mode 100644
index 000000000..3d8d9d119
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/GridCellRenderer.java
@@ -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
+ */
+public interface GridCellRenderer {
+ /**
+ * 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);
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/HelpLink.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/HelpLink.java
new file mode 100644
index 000000000..b5cfc22dc
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/HelpLink.java
@@ -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);
+ }
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/MessageBox.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/MessageBox.java
new file mode 100644
index 000000000..ca87df577
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/MessageBox.java
@@ -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.
+ *
+ * CSS Styles:
+ * messageBox
+ * messageBox-content
+ * messageBox-caption
+ *
+ * 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();
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/ProgressMonitor.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/ProgressMonitor.java
new file mode 100644
index 000000000..c91178517
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/ProgressMonitor.java
@@ -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
+ * progressMonitor
+ * 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);
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/ScrollableTable.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/ScrollableTable.java
new file mode 100644
index 000000000..4d694f73b
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/ui/ScrollableTable.java
@@ -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}.
+ *
+ * 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.
+ *
+ * The styles used
+ * table-caption : for the caption
+ * column-header : for the column headers
+ * row-odd : for odd numbered rows
+ * row-even : for even numbered rows
+ *
+ *
+ * @author Pinaki Poddar
+ *
+ * @param the type of data being displayed.
+ */
+public class ScrollableTable extends Composite {
+ private FlexTable _main;
+ private Grid _caption;
+ private FlexTable _header;
+ private ScrollPanel _scroll;
+ private List> _renderers;
+ private List _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>();
+
+ 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();
+ }
+
+ initWidget(vert);
+ }
+
+ @SuppressWarnings("unchecked")
+ public List 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 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 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);
+ }
+
+
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Ask.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Ask.java
new file mode 100644
index 000000000..7a16b2de1
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Ask.java
@@ -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();
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Bid.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Bid.java
new file mode 100644
index 000000000..0f25aa53a
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Bid.java
@@ -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();
+ }
+
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/LogStatement.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/LogStatement.java
new file mode 100644
index 000000000..4b653105d
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/LogStatement.java
@@ -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;
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Match.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Match.java
new file mode 100644
index 000000000..7894b186f
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Match.java
@@ -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 not 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 + "]";
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Sector.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Sector.java
new file mode 100644
index 000000000..55d27adc0
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Sector.java
@@ -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
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Stock.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Stock.java
new file mode 100644
index 000000000..00f1ebcaa
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Stock.java
@@ -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;
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Tradable.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Tradable.java
new file mode 100644
index 000000000..fa967bdb2
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Tradable.java
@@ -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;
+ }
+
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Trade.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Trade.java
new file mode 100644
index 000000000..be34f98d8
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Trade.java
@@ -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;
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Trader.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Trader.java
new file mode 100644
index 000000000..51da38738
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/Trader.java
@@ -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;
+ }
+
+
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/package.html b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/package.html
new file mode 100644
index 000000000..54d54776a
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/domain/package.html
@@ -0,0 +1,10 @@
+
+
+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 JRE Emulation Library GWT
+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.
+
+
\ No newline at end of file
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/override/org/apache/openjpa/util/LongId.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/override/org/apache/openjpa/util/LongId.java
new file mode 100644
index 000000000..8bd3abc25
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/override/org/apache/openjpa/util/LongId.java
@@ -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;
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/override/org/apache/openjpa/util/OpenJPAId.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/override/org/apache/openjpa/util/OpenJPAId.java
new file mode 100644
index 000000000..710f3dabc
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/override/org/apache/openjpa/util/OpenJPAId.java
@@ -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 ());
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/server/ExceptionAdapter.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/server/ExceptionAdapter.java
new file mode 100644
index 000000000..c69e925d7
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/server/ExceptionAdapter.java
@@ -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 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;
+ }
+
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/server/TradingServiceAdapterImpl.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/server/TradingServiceAdapterImpl.java
new file mode 100644
index 000000000..8c44e5c88
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/server/TradingServiceAdapterImpl.java
@@ -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 getStocks() {
+ try {
+ return new ArrayList(_del.getStocks());
+ } catch (Throwable e) {
+ throw translate(e);
+ }
+ }
+
+ public List getTrades(Timestamp from, Timestamp to) {
+ try {
+ return _del.getTrades(from, to);
+ } catch (Throwable e) {
+ throw translate(e);
+ }
+ }
+
+ public List 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 matchAsk(Ask ask) {
+ try {
+ return new ArrayList(_del.matchAsk(ask));
+ } catch (Throwable e) {
+ throw translate(e);
+ }
+ }
+
+ public List matchBid(Bid bid) {
+ try {
+ return new ArrayList(_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 getLog() {
+ try {
+ return _del.getLog();
+ } catch (Throwable e) {
+ throw translate(e);
+ }
+ }
+
+ RuntimeException translate(Throwable t) {
+ t.printStackTrace();
+ return new ExceptionAdapter().translate(t);
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/BufferedLog.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/BufferedLog.java
new file mode 100644
index 000000000..41020052d
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/BufferedLog.java
@@ -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.
+ *
+ * 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 CHANNELS = Arrays.asList(OpenJPAConfiguration.LOG_QUERY, JDBCConfiguration.LOG_SQL);
+
+ private static LinkedList _messageModel;
+ static {
+ _messageModel = new LinkedList();
+ }
+
+ 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 get() {
+ List result = new ArrayList(_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;
+ }
+
+ }
+
+
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/Exchange.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/Exchange.java
new file mode 100644
index 000000000..a0232acf0
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/Exchange.java
@@ -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 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 matchBid(Bid bid) {
+ EntityManager em = getEntityManager();
+ begin();
+ TypedQuery q = em.createQuery(MATCH_BID, Match.class)
+ .setParameter("bid", bid);
+ List matches = q.getResultList();
+ commit();
+ return matches;
+ }
+
+ public List matchAsk(Ask ask) {
+ EntityManager em = getEntityManager();
+ begin();
+ TypedQuery q = em.createQuery(MATCH_ASK, Match.class)
+ .setParameter("ask", ask);
+ List 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 getTrades(Timestamp from, Timestamp to) {
+ EntityManager em = getEntityManager();
+ begin();
+ List result = em.createQuery(QUERY_TRADE_BY_PERIOD, Trade.class)
+ .setParameter("from", from.getNanos())
+ .setParameter("to", to.getNanos())
+ .getResultList();
+ commit();
+ return result;
+ }
+
+ public List 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 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 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 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 getStocks() {
+ EntityManager em = getEntityManager();
+ begin();
+ List stocks = em.createQuery(GET_ALL_STOCKS, Stock.class).getResultList();
+ commit();
+ return stocks;
+ }
+
+
+ public List getLog() {
+ if (log == null) {
+ return new ArrayList();
+ }
+ return log.get();
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ }
+
+ static Map addLog(Map config) {
+ if (config == null) {
+ config = new HashMap();
+ }
+ config.put("openjpa.Log", BufferedLog.class.getName());
+ return config;
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/MarketFeed.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/MarketFeed.java
new file mode 100644
index 000000000..1f375b0eb
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/MarketFeed.java
@@ -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 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();
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/MockTradingService.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/MockTradingService.java
new file mode 100644
index 000000000..962d5ff73
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/MockTradingService.java
@@ -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 _asks = new ArrayList();
+ List _bids = new ArrayList();
+ List _trades = new ArrayList();
+ List _stocks = new ArrayList();
+ List _traders = new ArrayList();
+ List _logs = new ArrayList();
+ 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 getLog() {
+ int from = Math.max(_logs.size()-5, 0);
+ List result = new ArrayList();
+ 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 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 getTrades(Timestamp from, Timestamp to) {
+ return null;
+ }
+
+ @Override
+ public List 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 matchAsk(Ask ask) {
+ List result = new ArrayList();
+ for (Bid bid : _bids) {
+ if (matches(ask, bid)) {
+ result.add(new Match(ask, bid));
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public List matchBid(Bid bid) {
+ List result = new ArrayList();
+ 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() {
+ }
+
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/PersistenceService.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/PersistenceService.java
new file mode 100644
index 000000000..14c186243
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/PersistenceService.java
@@ -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
+ * per-thread persistence context
+ * 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 thread = new ThreadLocal();
+ private ReentrantLock lock = new ReentrantLock();
+
+ protected PersistenceService(String unit) {
+ this(unit, false, PersistenceContextType.EXTENDED, null);
+ }
+
+ protected PersistenceService(String unit, boolean managed, PersistenceContextType scope,
+ Map 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);
+ }
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/TradeEvent.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/TradeEvent.java
new file mode 100644
index 000000000..84fa2368b
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/TradeEvent.java
@@ -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;
+ }
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/TradingService.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/TradingService.java
new file mode 100644
index 000000000..975175163
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/TradingService.java
@@ -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 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 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 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 getTrades(Timestamp from, Timestamp to);
+
+ /**
+ * Gets the trades executed by the given trader between the given time periods.
+ * @param from
+ * @param to
+ * @return
+ */
+ List 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 getLog();
+}
diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/slice/SectorDistributionPolicy.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/slice/SectorDistributionPolicy.java
new file mode 100644
index 000000000..4eb294a43
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/service/slice/SectorDistributionPolicy.java
@@ -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 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;
+ }
+
+}
diff --git a/openjpa-examples/opentrader/src/main/resources/css/OpenTrader.css b/openjpa-examples/opentrader/src/main/resources/css/OpenTrader.css
new file mode 100644
index 000000000..d720f45e1
--- /dev/null
+++ b/openjpa-examples/opentrader/src/main/resources/css/OpenTrader.css
@@ -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;
+}
+