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