As stated earlier, the generic push event model exists such that events are delivered from supplier to the event channel and then directly to the consumer. The event itself is modeled using an Any object and can therefore represent any entity in the CORBA universe.
To begin our look at the generic push event model, let’s examine the IDL in Listing 31.2.
Listing 31.2 contains abbreviated versions of the CosEventComm and
CosEventChannelAdmin modules, which define all interfaces and operations needed for sending or receiving events. The CosEventComm module contains interfaces that deal directly with the propagation of events. The CosEventChannelAdmin module contains interfaces that model the managerial aspects of event management, specifically connecting to the channel itself.
For clarity, all interfaces that deal with the generic pull event model have been removed from Listing 31.2. These interfaces are covered in the next section and are also
contained in their entirety on the CD-ROM.
LISTING 31.2 SECTIONS OF THE CosEventComm AND CosEventChannelAdmin
MODULES DEALING WITH THE GENERIC PUSH EVENT MODEL module CosEventComm {
exception Disconnected{};
interface PushConsumer {
void push (in any data) raises(Disconnected);
void disconnect_push_consumer();
};
interface PushSupplier {
void disconnect_push_supplier();
};
};
module CosEventChannelAdmin { exception AlreadyConnected {};
exception TypeError {};
interface ProxyPushConsumer:CosEventComm::PushConsumer {
void connect_push_supplier(in CosEventComm::PushSupplier
push_supplier) raises(AlreadyConnected);
};
interface ProxyPushSupplier: CosEventComm::PushSupplier { void connect_push_consumer(in CosEventComm::PushConsumer
push_consumer)
raises(AlreadyConnected,TypeError);
};
interface ConsumerAdmin {
ProxyPushSupplier obtain_push_supplier();
ProxyPullSupplier obtain_pull_supplier();
};
interface SupplierAdmin {
ProxyPushConsumer obtain_push_consumer();
ProxyPullConsumer obtain_pull_consumer();
};
interface EventChannel {
ConsumerAdmin for_consumers();
SupplierAdmin for_suppliers();
void destroy();
};
};
Starting with the event channel, you’ll note an interface in the CosEventChannelAdmin module titled EventChannel. An object implementing this interface would be
instantiated when the event channel is started; suppliers and consumers would then use
the ORB to bind to the object. After binding to the EventChannel object, event suppliers obtain a SupplierAdmin object by invoking the for_suppliers() operation, and event consumers obtain a ConsumerAdmin object by invoking the for_consumers() operation. Through the xxx""" objects, suppliers and consumers actually connect to the channel. Once connected to the channel, suppliers post events to the channel and consumers receive events from the channel.
Moving beyond the event channel, the CosEventComm module defines interfaces implemented by the event suppliers and consumers. The PushConsumer interface is implemented by consumers and defines two operations that are potentially invoked by the event supplier. The push() operation is invoked by the event supplier when an event occurs, and the disconnect_push_consumer() operation is invoked by the event supplier or channel if the connection is broken.
As a demonstration of the generic push event model, the next few code listings build a stock portfolio manager application. The server side of the application attaches itself to the event channel and posts random quote updates to the channel. With quote updates being posted to the event channel, the client side of the application manages a portfolio and represents the active value using the data posted to the event channel. Because the server posts quote updates that represent stocks that might not be present in the
portfolio, it’s the burden of the client to filter out unwanted events.
Because this application allows client and server communication to occur only through the event channel, the amount of IDL needed is rather slim. Both the client and server expose no operations, and only the object modeling the stock symbol and price needs to be represented. Listing 31.3 contains the IDL for the QuoteUpdateI interface, which has attributes for stock symbol and stock price.
LISTING 31.3 THE IDL FOR THE STOCK SYMBOL AND PRICE INFORMATION
interface QuoteUpdateI { attribute string sSymbol;
attribute float fNewPrice;
};
With the IDL defined for the quote information, it’s now possible to build a server to post quote events. Listing 31.4 contains the GenericPushQuoteServer class, which is charged with sending all events. When instantiated, it attaches itself to the event channel and posts a stream of events. As you examine Listing 31.4, pay specific attention to the code in the run() method, because the actual event posting is contained there.
LISTING 31.4 THE GenericPushQuoteServer CLASS POSTS EVENTS TO THE EVENT CHANNEL
import java.util.*;
import org.omg.CosEventComm.*;
import org.omg.CosEventChannelAdmin.*;
import org.omg.CORBA.*;
/**
* The GenericPushQuoteServer class posts random quote values
* to the event channel.
*/
public final class GenericPushQuoteServer extends _PushSupplierImplBase implements Runnable {
private String[] __sSymbols = {"INKT", "MSFT",
"ORCL",
"CNWK", "XCIT",
"PSFT",
"LU", "CPQ","WMT",
"DIS"};
private Random _random;
private Thread _thread;
private org.omg.CORBA.ORB _orb;
private org.omg.CORBA.BOA _boa;
private ProxyPushConsumer _pushConsumer;
public GenericPushQuoteServer() { // bind to the orb
_orb = ORB.init();
// obtain a reference to the boa _boa = _orb.BOA_init();
_random = new Random();
// bind to the event channel
EventChannel channel = EventChannelHelper.bind(_orb);
// obtain a ProxyPushConsumer object.
_pushConsumer =
channel.for_suppliers().obtain_push_consumer();
// connect as an event supplier
try{ _pushConsumer.connect_push_supplier(this); } catch( AlreadyConnected ac ) {}
// start sending events in a unique thread _thread = new Thread(this);
_thread.start();
} /**
* Invoked when we are disconnected from the
* event channel.
*/
public void disconnect_push_supplier() { try{ _boa().deactivate_obj(this); } catch( SystemException e) { }
} /**
* Chooses (at random) one of the ten
* supported symbols.
*/
private String getRandomSymbol() {
return _sSymbols[Math.abs(_random.nextInt() % 10) -0];
} /**
* Generates a random value for a quote
* between 0 and 300
*/
private float getRandomPrice() {
return Math.abs(_random.nextInt() % 300) -0;
} /**
* Creates a new QuoteUpdateI object with
* random values
*/
private QuoteUpdateI obtainRandomQuoteUpdate() { QuoteUpdateI quote =
new QuoteUpdate(getRandomSymbol(), getRandomPrice());
_boa.obj_is_ready(quote);
return quote;
}
public void run() { while(true) {
try{ _thread.sleep(1000); } catch( Exception e ) {}
try { // obtain an Any object from the ORB org.omg.CORBA.Any eventObject =
_orb.create_any();
// add a QuoteUpdateI object to the Any object
eventObject.insert_Object(obtainRandomQuoteUpdate());
// post the event
pushConsumer.push(eventObject);
}
catch( Disconnected e) { } catch( SystemException e) {
disconnect_push_supplier();
} } }
public static void main(String[] args)) { GenericPushQuoteServer server = new GenericPushQuoteServer();
} }
Moving from the server to the client, Listing 31.5 contains the GenericPushListener class. This class attaches itself to the event channel and is notified of all new stock prices. When a price is obtained, the portfolio is examined for the presence of the associated symbol. If the holding is present in the portfolio, its value is updated;
otherwise, the event is ignored.
LISTING 31.5 THE GenericPushListener CLASS LISTENS FOR EVENTS POSTED TO THE EVENT CHANNEL
import java.util.*;
import org.omg.CosEventComm.*;
import org.omg.CosEventChannelAdmin.*;
/**
* The GenericPushListener class is notified via its
* push() method whenever a new price is available
* for a given quote.
*/
public class GenericPushListener extends _PushConsumerImplBase {
private Hashtable _hshPortfolio;
private PortfolioGUI _portfolioGUI;
public GenericPushListener(Hashtable hshPortfolio, PortfolioGUI portfolioGUI) { _hshPortfolio = hshPortfolio;
_portfolioGUI = portfolioGUI;
// bind to the orb
org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init();
// obtain a reference to the boa
org.omg.CORBA.BOA boa = orb.BOA_init();
// obtain a reference to the event channel
EventChannel channel = EventChannelHelper.bind(orb);;
// connect as a listener for push events ProxyPushSupplier pushSupplier =
channel.for_consumers().obtain_push_supplier();;
try{ pushSupplier.connect_push_consumer(this); } catch( AlreadyConnected ac ) {}
} /**
* The push() method is invoked by the event channel * when a new event is pushed into the channel queue.
*/
public void push(org.omg.CORBA.Any data) throws Disconnected { // extract the event data
org.omg.CORBA.Object object = data.extract_Object();
// narrow (cast) the event data as a QuoteUpdateI object
QuoteUpdateI quoteUpdate = QuoteUpdateIHelper.narrow(object);
// check to see if the updated price represents // an active holding.
String sSymbol = quoteUpdate.sSymbol();
if(_hshPortfolio.containsKey(sSymbol)) { synchronized(_hshPortfolio) {
// update the stock value in the portfolio StockOwnership stock =
(StockOwnership)_hshPortfolio.get(sSymbol);
stock.setPrice(quoteUpdate.fNewPrice());
}
// repaint the portfolio screen _portfolioGUI.updatePortfolio();
} } /**
* The disconnect_push_consumer() method is invoked * by the event channel when we are disconnected * from the event channel.
*/
public void disconnect_push_consumer() { }
}
Whereas Listing 31.5 contains the actual code used to listen for events, Listing 31.6 contains the code used to render all information onscreen. The PortfolioGUI class is the main point of entry for the application, and it displays, in grid format, current values for all holdings. The screen presents buttons that enable the human user to either buy or sell stocks. When a user requests that a buy or sell be performed, the BuyDialog or SellDialog (respectively) handle the user interaction. Finally, the StockOwnership class represents a holding (symbol, shares owned, and share price).
LISTING 31.6 THE PortfolioGUI, BuyDialog, SellDialog, AND
StockOwnership CLASSES FACILITATE DISPLAY OF LIVE PORTFOLIO INFORMATION
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import jclass.bwt.*;
/**
* The PortfolioGUI class represents a simple
* GUI for our portfolio client.
*/
public final class PortfolioGUI extends Frame { private JCMultiColumnList _mlstPortfolio;
private Hashtable _hshPortfolio;
private Button _btnBuy;
private Button _btnSell;
public PortfolioGUI() {
super("Stock Portfolio");
_mlstPortfolio = new JCMultiColumnList();
_mlstPortfolio.setColumnButtons(column_labels);
Panel pnlButtons = new Panel();
pnlButtons.setLayout(new FlowLayout(FlowLayout.LEFT));
pnlButtons.add(_btnBuy = new Button("Buy"));
pnlButtons.add(_btnSell = new Button("Sell"));
_btnBuy.addActionListener(new BuySellListener("BUY", this));
_btnSell.addActionListener(new BuySellListener("SELL", this));
setLayout(new BorderLayout());
add(pnlButtons, "North");
add(_mlstPortfolio, "Center");
_hshPortfolio = new Hashtable();
_hshPortfolio.put("INKT", new StockOwnership("INKT", 57.5f,100f));
_hshPortfolio.put("MSFT", new StockOwnership("MSFT", 113.8f, 54521f));
_hshPortfolio.put("ORCL", new StockOwnership("ORCL", 24.6f, 5485f));
_hshPortfolio.put("CNWK", new StockOwnership("CNWK", 64.75f, 300f));
_hshPortfolio.put("XCIT", new StockOwnership("XCIT", 44.62f,7164f));
updatePortfolio();
// listen for changes
GenericPushListener listener =
new GenericPushListener(_hshPortfolio, this);
}
public void updatePortfolio() {
_mlstPortfolio.setBatched(true); // don"t repaint on add _mlstPortfolio.clear();
Enumeration e = _hshPortfolio.elements();
while(e.hasMoreElements()) {
StockOwnership stock = (StockOwnership)e.nextElement();
StringBuffer sbBuilder = new StringBuffer();
sbBuilder.append(stock.getSymbol());
sbBuilder.append("|");
sbBuilder.append(stock.getSharesOwned());
sbBuilder.append("|");
sbBuilder.append(stock.getPrice());
sbBuilder.append("|");
sbBuilder.append(stock.getSharesOwned()*stock.getPrice());
_mlstPortfolio.addItem(sbBuilder.toString(), "|");
}
_mlstPortfolio.setBatched(false); // repaint all }
/**
* Invoked when a user OKs out of a
* BUY dialog box. Indicates that the
* specified number of shares of the stock
* identified by the specified symbol were
* purchased. The purchase price is assumed
* to be the current price.
*/
public void buyOccurred(String sSymbol, float fShares) { StockOwnership stock =
(StockOwnership)_hshPortfolio.get(sSymbol);
if(stock != null)
stock.setSharesOwned(stock.getSharesOwned()+fShares);
updatePortfolio();
}
/**
* Invoked when a user OKs out of a
* SELL dialog box. Indicates that the
* specified number of shares of the stock
* identified by the specified symbol were
* sold. The selling price is assumed
* to be the current price.
*/
public void sellOccurred(String sSymbol, float fShares) { StockOwnership stock =
(StockOwnership)_hshPortfolio.get(sSymbol);
if(stock != null)
stock.setSharesOwned(stock.getSharesOwned()-fShares);
updatePortfolio();
} /**
* Inner class used to listen for clicks on
* the buy or sell buttons.
*/
class BuySellListener implements ActionListener { private final int BUY = 1;
private final int SELL = 2;
private int _iMode = 0;
private PortfolioGUI _parent;
public BuySellListener(String sMode, PortfolioGUI parent) { if(sMode.equalsIgnoreCase("BUY")) _iMode = BUY;
else _iMode = SELL;
_parent = parent;
}
public void actionPerformed(ActionEvent ae) { Dialog dialog = null;
if(_iMode = BUY) {
dialog = new BuyDialog(_parent, _hshPortfolio);
} else {
dialog = new SellDialog(_parent, _hshPortfolio);
}
dialog.pack();
dialog.setVisible(true);
} }
public final static void main(String[] args)) { PortfolioGUI gui = new PortfolioGUI();
gui.pack();
gui.setVisible(true);
} }
import java.awt.*;
import java.awt.event.*;
import java.util.*;
/**
* The BuyDialog class is a simple UI tool
* that allows users to buy additional shares
* in existing holdings.
*/
public class BuyDialog extends Dialog { private Choice _chcSymbols;
private TextField _txtShares;
private Button _btnOk;
private Button _btnCancel;
private PortfolioGUI _portfolioGUI;
public BuyDialog(PortfolioGUI portfolioGUI, Hashtable hshPortfolio) {
super(portfolioGUI, true);
setBackground(Color.white);
_portfolioGUI = portfolioGUI;
_chcSymbols = new Choice();
Enumeration e = hshPortfolio.keys();
_chcSymbols.add("");
while(e.hasMoreElements()) {
_chcSymbols.add(e.nextElement().toString());
}
Panel pnlLabel = new Panel();
pnlLabel.setLayout(new FlowLayout(FlowLayout.CENTER));
pnlLabel.add(new Label("Select A Symbol And Share Count"));
Panel pnlDecision = new Panel();
pnlDecision.setLayout(new FlowLayout(FlowLayout.LEFT));
pnlDecision.add(new Label("Symbol: ", Label.RIGHT));
pnlDecision.add(_chcSymbols);
pnlDecision.add(new Label(" "));
pnlDecision.add(new Label("Shares: ", Label.RIGHT));
pnlDecision.add(_txtShares = new TextField(15));
Panel pnlButtons = new Panel();
pnlButtons.setLayout(new FlowLayout(FlowLayout.RIGHT,5,5));
pnlButtons.add(_btnOk = new Button("Ok"));
pnlButtons.add(_btnCancel = new Button("Cancel"));
_btnOk.addActionListener(new ButtonListener(this, "OK"));
_btnCancel.addActionListener(new ButtonListener(this,
"CANCEL"));
setLayout(new GridLayout(3,1));
add(pnlLabel);
add(pnlDecision);
add(pnlButtons);
} /**
* Inner class used to listen for the event * caused when a button is clicked.
*/
class ButtonListener implements ActionListener { public int OK = 10;
public int CANCEL = 13;
private int _iMode;
private BuyDialog _parent;
public ButtonListener(BuyDialog parent, String sMode) {
_parent = parent;
if(sMode.equalsIgnoreCase("OK")) _iMode = OK;
else _iMode = CANCEL;
}
public void actionPerformed(ActionEvent ae) { if(_iMode = OK) {
try{ float fShares =
Float.valueOf(_txtShares.getText()).floatValue();
_portfolioGUI.buyOccurred(
_chcSymbols.getSelectedItem(), fShares);
}
catch( NumberFormatException nfe ) {}
}
_parent.setVisible(false);
} } }
import java.awt.*;
import java.awt.event.*;
import java.util.*;
/**
* The SellDialog class is a simple UI tool
* that allows users to sell shares
* in existing holdings.
*/
public class SellDialog extends Dialog {
private Hashtable _hshPortfolio;
private Choice _chcSymbols;
private TextField _txtShares;
private Button _btnOk;
private Button _btnCancel;
private PortfolioGUI _portfolioGUI;
public SellDialog(PortfolioGUI portfolioGUI, Hashtable hshPortfolio) {
super(portfolioGUI, true);
setBackground(Color.white);
_portfolioGUI = portfolioGUI;
_hshPortfolio = hshPortfolio;
_chcSymbols = new Choice();
Enumeration e = hshPortfolio.keys();
_chcSymbols.add("");
while(e.hasMoreElements()) {
_chcSymbols.add(e.nextElement().toString());
}
_chcSymbols.addItemListener(new ChoiceListener());
Panel pnlLabel = new Panel();
pnlLabel.setLayout(new FlowLayout(FlowLayout.CENTER));
pnlLabel.add(new Label("Select A Symbol And Share Count"));
Panel pnlDecision = new Panel();
pnlDecision.setLayout(new FlowLayout(FlowLayout.LEFT));
pnlDecision.add(new Label("Symbol: ", Label.RIGHT));
pnlDecision.add(_chcSymbols);
pnlDecision.add(new Label(" "));
pnlDecision.add(new Label("Shares: ", Label.RIGHT));
pnlDecision.add(_txtShares = new TextField(15));
Panel pnlButtons = new Panel();
pnlButtons.setLayout(new FlowLayout(FlowLayout.RIGHT,5,5));
pnlButtons.add(_btnOk = new Button("Ok"));
pnlButtons.add(_btnCancel = new Button("Cancel"));
_btnOk.addActionListener(new ButtonListener(this, "OK"));
_btnCancel.addActionListener(new ButtonListener(this,
"CANCEL"));
setLayout(new GridLayout(3,1));
add(pnlLabel);
add(pnlDecision);
add(pnlButtons);
} /**
* Inner class used to listen for changes to the
* symbol being displayed in the Choice
*/
class ChoiceListener implements ItemListener {
public void itemStateChanged(ItemEvent ie) { }
} /**
* Inner class used to listen for the event * caused when a button is clicked.
*/
class ButtonListener implements ActionListener { public int OK = 10;
public int CANCEL = 13;
private int _iMode;
private SellDialog _parent;
public ButtonListener(SellDialog parent, String sMode) {
_parent = parent;
if(sMode.equalsIgnoreCase("OK")) _iMode = OK;
else _iMode = CANCEL;
}
public void actionPerformed(ActionEvent ae) { if(_iMode = OK) {
try{ float fShares =
Float.valueOf(_txtShares.getText()).floatValue();
_portfolioGUI.sellOccurred(
_chcSymbols.getSelectedItem(),
fShares);
}
catch( NumberFormatException nfe ) {}
}
_parent.setVisible(false);
} } } /**
* Class used to represent ownership
* in a unique stock
*/
public class StockOwnership {
private String _sSymbol = null;
private float _fPrice = 0f;
private float _fsharesOwned = 0f;
public StockOwnership() {
}
public StockOwnership(String sSymbol, float fPrice,
float fsharesOwned) { _sSymbol = sSymbol;
_fPrice = fPrice;
_fsharesOwned = fsharesOwned;
}
public String getSymbol() { return _sSymbol;
}
public float getPrice() { return _fPrice;
}
public float getSharesOwned() { return _fsharesOwned;
}
public void setSymbol(String sSymbol) { _sSymbol = sSymbol;
}
public void setPrice(float fPrice) { _fPrice = fPrice;
}
public void setSharesOwned(float fsharesOwned) { _fsharesOwned = fsharesOwned;
} }
To run the application, you’ll need the Inprise Visibroker ORB installed, along with the Inprise Event Channel software and a GUI library called JClass BWT that is also on the CD-ROM. The JClass package contains a collection of UI widgets. In the current example we use the grid widget. If you haven’t installed the software yet, do so now. With all the software installed, first launch the ORB runtime using the following command:
osagent
You’ll now need to launch the event channel using this command:
vbj com.visigenic.vbroker.services.CosEvent.Channel EventChannelName
The parameter passed to the event channel application is a logical name associated with this unique channel. If your application calls for the use of multiple event channels, each would have a unique name, and all channel bind operations would specify the name of vbj GenericPushQuoteServer
and the client by typing vbj PortfolioGUI
As the application runs, you’ll see rapid changes in the value of all holdings. Because a stock price is randomly recalculated to fall between $0–$300, chances are you’ll not have the same portfolio value for long. Although these dramatic shifts are, thankfully, not indicative of market performance, they do represent a potential situation in which the markets might get all confused on January 1, 2000. The hectic portfolio is shown in Figure 31.2.
Figure 31.2: The stock portfolio application displaying ticker values.