/*
* 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);
}
}
}