diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/OpenTrader.gwt.xml b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/OpenTrader.gwt.xml new file mode 100644 index 000000000..d86061c0f --- /dev/null +++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/OpenTrader.gwt.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/FormatUtil.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/FormatUtil.java new file mode 100644 index 000000000..5a026c9ee --- /dev/null +++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/FormatUtil.java @@ -0,0 +1,97 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.trader.client; + +import com.google.gwt.i18n.client.NumberFormat; +import com.google.gwt.user.client.ui.HTML; + +/** + * A set of static utilities to create decorated HTML labels for price and price changes. + * + * @author Pinaki Poddar + * + */ +public class FormatUtil { + public static final NumberFormat priceFormat = NumberFormat.getFormat("#,##0.00"); + public static final NumberFormat changeFormat = NumberFormat.getFormat("+#,##0.00;-#,##0.00"); + public static final NumberFormat volumeFormat = NumberFormat.getFormat("#,##0"); + + /** + * Creates a HTML for the formatted price without any style. + */ + public static HTML formatPrice(double price) { + return formatPrice(price, false); + } + + /** + * Creates a HTML label for the formatted price with a style based on the signum of the price. + * The positive and negative values are formatted with CSS styles positive and + * negative respectively. + */ + public static HTML formatPrice(double price, boolean style) { + HTML label = new HTML(); + label.setText(priceFormat.format(price)); + if (style) + label.addStyleName(price >= 0 ? "positive" : "negative"); + return label; + } + + /** + * Creates a HTML label for the given integer. + */ + public static HTML formatVolume(int volume) { + HTML label = new HTML(); + label.setText(volumeFormat.format(volume)); + return label; + } + + /** + * Creates a HTML label for the difference of two given numbers and applies style based + * on the sign of the difference. Optionally adds percentage change. + * + * @param p1 first value + * @param p2 second value + * @param pct if true, adds a percentage change + */ + public static HTML formatChange(double p1, double p2, boolean pct) { + String raw = changeFormat.format(p1-p2); + if (pct && p1 != 0) { + double delta = p1 - p2; + String pctraw = changeFormat.format(100*delta/p1)+"%"; + raw += " (" + pctraw + ")"; + } + String style = p1 >= p2 ? "positive" : "negative"; + HTML html = new HTML(); + html.setText(raw); + html.addStyleName(style); + return html; + } + + /** + * Creates a HTML label for the given value and applies style based on the sign. + */ + public static HTML formatChange(double c) { + HTML html = new HTML(); + String raw = changeFormat.format(c); + String style = c >= 0 ? "positive" : "negative"; + html.setText(raw); + html.addStyleName(style); + return html; + } +} diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/LoginDialog.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/LoginDialog.java new file mode 100644 index 000000000..2df6cf51d --- /dev/null +++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/LoginDialog.java @@ -0,0 +1,138 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.trader.client; + +import java.util.List; + +import org.apache.openjpa.trader.client.ui.MessageBox; +import org.apache.openjpa.trader.client.ui.ProgressMonitor; +import org.apache.openjpa.trader.domain.Stock; +import org.apache.openjpa.trader.domain.Trader; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.DOM; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.FlexTable; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.TextBox; + +/** + * A dialog box for login a Trader. Once the trader's name is entered, this widget calls the server + * to start a session for the trader, gets all the tradable Stocks and passes that initialization data + * to the {@link OpenTrader main application} to {@link OpenTrader#init(Trader, List) initialize}. + * + *
+ * CSS styles used + *
  • login : for the main dialog box + *
  • login-caption: for the caption + * + * + * @author Pinaki Poddar + * + */ +public class LoginDialog extends PopupPanel { + private Trader trader; + private final OpenTrader session; + + public LoginDialog(final OpenTrader session) { + super(false, true); + + setAnimationEnabled(true); + this.session = session; + + final FlexTable table = new FlexTable(); + final HTML header = new HTML("  Welcome to OpenTrader  "); + final Label label = new Label("Please enter name:"); + DOM.setStyleAttribute(label.getElement(), "textAlign", "right"); + final TextBox traderName = new TextBox(); + traderName.setText("OpenTrader"); + final Button enter = new Button("Enter"); + + addStyleName("login"); + table.addStyleName("login"); + label.addStyleName("login"); + header.addStyleName("login-caption"); + + table.setWidget(0, 0, header); + table.setWidget(2, 0, label); + table.setWidget(2, 1, traderName); + table.setWidget(4, 1, enter); + table.getFlexCellFormatter().setColSpan(0, 0, 2); + + enter.setEnabled(true); + traderName.setFocus(true); + + enter.addClickHandler(new ClickHandler() { + public void onClick(ClickEvent event) { + if (traderName.getText().trim().length() == 0) { + MessageBox.alert("Trader's name must not be empty."); + return; + } + hide(); + ProgressMonitor.showProgress("Connecting to OpenTrader Server..."); + session.getService().login(traderName.getText(), new LoginCallback()); + } + }); + setWidget(table); + } + + /** + * --------------------------------------------------------------------------------- + * Asynchronous RPC service callbacks + * --------------------------------------------------------------------------------- + */ + + /** + * Logs in a [@link Trader} and then invokes another RPC service to get the list of tradable stocks. + * This pattern of calling one RPC from another addresses sequential execution of asynchronous + * callbacks. This pattern is necessary when the second RPC depends in some way to the result of + * the first RPC. + * + */ + class LoginCallback implements AsyncCallback { + public void onFailure(Throwable caught) { + ProgressMonitor.stop(); + session.handleError(caught); + } + + public void onSuccess(Trader result) { + trader = result; + session.getService().getStocks(new InitializeStocks()); + } + } + + /** + * Initializes the tradable stocks followed by the main application. + * + */ + public class InitializeStocks implements AsyncCallback> { + public void onFailure(Throwable caught) { + session.handleError(caught); + } + + public void onSuccess(List stocks) { + ProgressMonitor.stop(); + session.init(trader, stocks); + } + } +} diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/MarketDataPanel.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/MarketDataPanel.java new file mode 100644 index 000000000..2a733c3a7 --- /dev/null +++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/MarketDataPanel.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.trader.client; + +import java.util.List; + +import org.apache.openjpa.trader.client.event.ServiceEvent; +import org.apache.openjpa.trader.client.event.ServiceEventHandler.UpdateStockHandler; +import org.apache.openjpa.trader.client.ui.GridCellRenderer; +import org.apache.openjpa.trader.client.ui.ScrollableTable; +import org.apache.openjpa.trader.domain.Stock; + +import com.google.gwt.user.client.Timer; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Label; +import com.google.gwt.user.client.ui.Widget; + +/** + * Displays the current Stock prices and updates periodically. + * + * + * @author Pinaki Poddar + * + */ +public class MarketDataPanel extends ScrollableTable implements UpdateStockHandler { + private final OpenTrader session; + private Timer refreshTimer; + private static int refreshInterval = 60*1000; + + public MarketDataPanel(final OpenTrader session, final int w, final int h) { + super("Market Prices (Updated every " + refreshInterval/1000 + "s)", w, h, true); + this.session = session; + + session.registerHandler(ServiceEvent.StockUpdated.TYPE, this); + + setColumnHeader(0, "Symbol", "25%"); + setColumnHeader(1, "Price", "25%"); + setColumnHeader(2, "Change", "50%"); + + // Stock Symbol + setRenderer(0, new GridCellRenderer() { + public Widget render(Stock stock) { + return new Label(stock.getSymbol()); + } + }); + + // Current Market Price + setRenderer(1, new GridCellRenderer() { + public Widget render(Stock stock) { + return FormatUtil.formatPrice(stock.getMarketPrice()); + } + }); + + // Percent Change since last update + setRenderer(2, new GridCellRenderer() { + public Widget render(Stock stock) { + return FormatUtil.formatChange(stock.getMarketPrice(), stock.getLastPrice(), true); + } + }); + } + + /** + * Sets the interval to refresh the stock data from the server. + * + * @param interval period in milliseconds. + */ + public void setRefreshInterval(int interval) { + refreshInterval = interval; + setCaption("Market Prices (Updated every " + refreshInterval/1000 + "s)"); + if (refreshTimer != null) + refreshTimer.scheduleRepeating(refreshInterval); + } + + /** + * Gets the interval (in milliseconds) to refresh the stock data from the server. + */ + public int getRefreshInterval() { + return refreshInterval; + } + + /** + * Starts a periodic update of the stocks from the server. + */ + public void startStockWatcher() { + if (refreshTimer != null) + return; + // Setup timer to refresh list automatically. + refreshTimer = new Timer() { + @Override + public void run() { + session.getService().getStocks(new UpdateStocks()); + } + }; + refreshTimer.run(); + refreshTimer.scheduleRepeating(refreshInterval); + } + + /** + * Starts periodic update of the stocks from the server. + */ + public void stopStockWatcher() { + if (refreshTimer == null) + return; + refreshTimer.cancel(); + refreshTimer = null; + } + + /** + * --------------------------------------------------------------------------------- + * Service Event Response Management + * --------------------------------------------------------------------------------- + */ + + /** + * Updates the stock data. + */ + @Override + public void onStockUpdated(ServiceEvent.StockUpdated event) { + update(event.getPayload(), null); + } + + + /** + * --------------------------------------------------------------------------------- + * Asynchronous RPC service callbacks + * --------------------------------------------------------------------------------- + */ + + /** + * Periodically update the stocks and notifies the listeners via the + * {@link OpenTrader#fireEvent(com.google.gwt.event.shared.GwtEvent) mediator}. + * In this case, one of the listeners is this widget itself. Still the + * {@link ServiceEvent.StockUpdated service event} is propagated via the + * mediator (so that others can listen as well). + * + */ + public class UpdateStocks implements AsyncCallback> { + public void onFailure(Throwable caught) { + session.handleError(caught); + } + + public void onSuccess(List result) { + int n = result.size(); + for (int i = 0; i < n; i++) { + session.fireEvent(new ServiceEvent.StockUpdated(result.get(i))); + } + } + } +} diff --git a/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/MatchWindow.java b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/MatchWindow.java new file mode 100644 index 000000000..508659dbd --- /dev/null +++ b/openjpa-examples/opentrader/src/main/java/org/apache/openjpa/trader/client/MatchWindow.java @@ -0,0 +1,144 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.trader.client; + +import java.util.List; + +import org.apache.openjpa.trader.client.event.ServiceEvent; +import org.apache.openjpa.trader.domain.Ask; +import org.apache.openjpa.trader.domain.Match; +import org.apache.openjpa.trader.domain.Tradable; +import org.apache.openjpa.trader.domain.Trade; +import org.apache.openjpa.trader.domain.Trader; + +import com.google.gwt.event.dom.client.ClickEvent; +import com.google.gwt.event.dom.client.ClickHandler; +import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.ui.Button; +import com.google.gwt.user.client.ui.FlexTable; +import com.google.gwt.user.client.ui.FlowPanel; +import com.google.gwt.user.client.ui.HTML; +import com.google.gwt.user.client.ui.PopupPanel; +import com.google.gwt.user.client.ui.RadioButton; + +/** + * A popup presents a list of matching Tradables and lets the user select one of them + * to commit a trade. + * + * @author Pinaki Poddar + * + */ +public class MatchWindow extends PopupPanel { + private final OpenTrader session; + + public MatchWindow(final OpenTrader session, final Tradable tradable, final List 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 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 _type; + + public TradeHistoryPanel(final OpenTrader session, Class 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; +} +