Chapter 26. Swing and CORBA
In this chapter:
26.1 Java 2 and CORBA
CORBA (Common Object Request Broker Architecture) was developed by the Object Management Group (OMG), a large industry consortium, as an attempt to satisfy the need for interoperability among the rapidly proliferating number of hardware and software products available today. The Java 2 platform includes CORBA-related packages (
org.omg.CORBA, org.omg.CosNaming, and their sub-packages) which can be used to build CORBA/Java applications and applets.
Note: The latest revision of the CORBA architecture specification adopted by OMG (version 2.2 in February 1998) is available at http://www.omg.org/library/c2indx.html.
A detailed description of both CORBA and the CORBA-related Java 2 packages lies far beyond the scope of this book. However, we feel that the potential for this technology is strong enough to warrant the inclusion of an example application showing how to implement a CORBA-enabled back end with Swing up front. For readers completely new to CORBA, we suggest referencing the following URL before moving on: http://www.omg.org/corba/beginners.html.
Note: The current CORBA functionality in Java 2 is not complete. Only a naming service is implemented, and the API docs lack a significant amount of explanation.
The example in this chapter is presented in three parts and demonstrates how we can build a complete CORBA-enabled Java application using Swing as a front end, CORBA as middleware, and a database server as a backend. We will build this application using our stocks table example presented in chapter 18 as a base, and we will use a CORBA-enabled remote server to supply stock data.
26.2 Creating a CORBA interface
To start the construction of a CORBA-enabled application we first need to define an IDL interface which will be used to retrieve data from a remote server. The example in this section presents two such interfaces and two user-defined exceptions in one module IDL file.
Note: IDL stands for Interface Definition Language. This language is specified by OMG and is used define the interfaces to CORBA objects. Java 2 uses a subset of IDL which is called "Java IDL" (see
http://www.javasoft.com/products/jdk/idl/index.html for more details).The Code: MarketData.idl
see \Chapter26\1
module MarketDataApp { interface DataUnit { string getSymbol(); string getName(); double getLast(); double getOpen(); double getChange(); double getChangePr(); long getVolume(); }; exception CorbaSqlException { string reason; }; exception LoginException { }; interface MarketData { DataUnit getFirstData(in string name, in string password, in long year, in long month, in long day) raises(LoginException, CorbaSqlException); DataUnit getNextData() raises(CorbaSqlException); }; };
Java IDL is similar to Java and C++, but it does have some important distinctions. First, all parameters passed to the declared methods (see the
MarketData interface) must have a direction attribute: in, out, or inout. Second, the raises keyword is used to declare exceptions which may be thrown by the associated method.The
MarketDataApp Java IDL file shown above declares the following:The
The
MarketData interface delivers a sequence of DataUnit objects to a caller. The getFirstData() method returns the first DataUnit object available for a given year, month, and day, or null if no data is available. This method also takes a user's name and password as parameters, and is capable of throwing a LoginException or a CorbaSqlException. The getNextData() method returns the next avai! lable DataUnit object. By calling this method repeatedly we can retrieve all sequences of data objects (used to fill each row of our stocks table).A
LoginException exception will be thrown if the user's authentication fails.The
CorbaSqlException exception will be thrown to encapsulate an SQL exception thrown on the server side, and pass it to the caller.To compile this IDL file you need to download the
idltojava compiler. This tool is not included in the Java 2 release, but is freely available from Java Developers Connection web site at http://developer.java.sun.com/developer/earlyAccess/jdk12/idltojava.html. This tool should be placed in the Java/bin directory. Once there, we can run the following command:idltojava -fverbose -fno-cpp MarketData.idl
This command will generate a number of Java source files in a "MarketDataApp" sub-directory:
DataUnit.java
: Java interface (listed below) corresponding to our DataUnit CORBA interface.DataUnitHelper.java
: helper class for the DataUnit interface. Helpers declare a set of useful static methods to help manage a given class, notably narrow(), which is the CORBA counterpart of a Java class cast.DataUnitHolder.java
: holder class for the DataUnit interface. Holders are required for CORBA operations that take out or inout arguments. Unlike CORBA in arguments, out and inout don't map directly to Java's pass-by-value semantics._DataUnitImplBase.java
: this abstract class provides a base implementation of a CORBA object which all concrete implementations of the DataUnit interface must extend._DataUnitStub.java
: this class represents the implementation of a client stub. It implements the DataUnit interface and provides the associated functionality for the client.MarketData.java
: Java interface (listed below) corresponding to our MarketData CORBA interface.MarketDataHelper.java
: helper class for MarketData interface.MarketDataHolder.java
: holder class for MarketData interface._MarketDataImplBase.java
: this abstract class provides a base implementation of a CORBA object which all concrete implementations of the MarketData interface must extend._MarketDataStub.java
: this class represents the implementation of client stub. It implements our MarketData interface and provides the associated functionality for the client.CorbaSqlException.java
: this class implements the SQL server-side exception.CorbaSqlExceptionHelper.java
: helper class for CorbaSqlException.CorbaSqlExceptionHolder.java
: holder class for CorbaSqlException.LoginException.java
: this class implements the authentication exception.LoginExceptionHelper.java
: helper class for LoginException class.LoginExceptionHolder.java
: holder class for LoginException class.26.4 Creating a CORBA server
Now that we have the compiler-generated source files it's time to implement the CORBA server which will run on a remote computer. First let's take a look at the
DataUnit interface describing a data object which can be transported to the caller. The following code is the compiler-generated DataUnit interface, and the associated DataUnitImpl class which implements this interface:The Code: DataUnit.java
see \Chapter26\MarketDataApp\1
/* * File: ./MARKETDATAAPP/DATAUNIT.JAVA * From: MARKETDATA.IDL * Date: Tue Feb 09 11:31:12 1999 * By: idltojava Java IDL 1.2 Aug 18 1998 16:25:34 */ package MarketDataApp; public interface DataUnit extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity { String getSymbol(); String getName(); double getLast(); double getOpen(); double getChange(); double getChangePr(); int getVolume(); }
The Code: DataUnitImpl.java
see \Chapter26\MarketDataApp\1
package MarketDataApp; import java.sql.*; public class DataUnitImpl extends _DataUnitImplBase { protected String m_symbol; protected String m_name; protected double m_last; protected double m_open; protected double m_change; protected double m_changePr; protected long m_volume; public DataUnitImpl(ResultSet results) throws SQLException { m_symbol = results.getString(1); m_name = results.getString(2); m_last = results.getDouble(3); m_open = results.getDouble(4); m_change = results.getDouble(5); m_changePr = results.getDouble(6); m_volume = results.getLong(7); } public String getSymbol() { return m_symbol; } public String getName() { return m_name; } public double getLast() { return m_last; } public double getOpen() { return m_open; } public double getChange() { return m_change; } public double getChangePr() { return m_changePr; } public int getVolume() { return (int)m_volume; } }
The compiler-generated
DataUnit interface contains seven getXX() methods intended for retrieving data fields from the implementor. Note that these fields directly correspond to the data fields of our StockData class from chapter 18.Class
DataUnitImpl extends the compiler-generated _DataUnitImplBase class (not listed here), which, in turn, implements the DataUnit interface. The seven data fields declared in the DataUnitImpl class correspond to the getXX() methods of the DataUnit interface. The DataUnitImpl constructor takes an instance of java.sql.ResultSet as parameter, and reads data from each record of the given resu! lt set.
Note: CORBA does not support 8-byte integers, so we are forced to pass the
m_volume variable as int.The Code: MarketData.java
see \Chapter26\MarketDataApp\1
/* * File: ./MARKETDATAAPP/MARKETDATA.JAVA * From: MARKETDATA.IDL * Date: Tue Feb 09 11:31:12 1999 * By: idltojava Java IDL 1.2 Aug 18 1998 16:25:34 */ package MarketDataApp; public interface MarketData extends org.omg.CORBA.Object, org.omg.CORBA.portable.IDLEntity { MarketDataApp.DataUnit getFirstData(String name, String password, int year, int month, int day) throws MarketDataApp.LoginException, MarketDataApp.CorbaSqlException; MarketDataApp.DataUnit getNextData() throws MarketDataApp.CorbaSqlException; }
The Code: MarketDataServer.java
see \Chapter26\MarketDataApp\1
package MarketDataApp; import java.sql.*; import java.util.*; import org.omg.CosNaming.*; import org.omg.CosNaming.NamingContextPackage.*; import org.omg.CORBA.*; public class MarketDataServer extends _MarketDataImplBase { protected Connection m_conn; protected Statement m_stmt; protected ResultSet m_results; public MarketDataServer() { try { // Load the JDBC-ODBC bridge driver Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); } catch (Exception e) { e.printStackTrace(); } } public DataUnit getFirstData(String name, String password, int year, int month, int day) throws MarketDataApp.LoginException, MarketDataApp.CorbaSqlException { if (!name.equals("CORBA") || !password.equals("Swing")) throw new MarketDataApp.LoginException(); String query = "SELECT data.symbol, symbols.name, "+ "data.last, data.open, data.change, data.changeproc, "+ "data.volume FROM DATA INNER JOIN SYMBOLS "+ "ON DATA.symbol = SYMBOLS.symbol WHERE "+ "month(data.date1)="+month+" AND day(data.date1)="+day+ " AND year(data.date1)="+year; try { m_conn = DriverManager.getConnection( "jdbc:odbc:Market", "admin", ""); m_stmt = m_conn.createStatement(); m_results = m_stmt.executeQuery(query); if (m_results.next()) return new DataUnitImpl(m_results); else { disconnect(); return null; } } catch (Exception e) { e.printStackTrace(); throw new CorbaSqlException(e.toString()); } } public DataUnit getNextData() throws MarketDataApp.CorbaSqlException { try { if (m_results != null && m_results.next()) return new DataUnitImpl(m_results); else { disconnect(); return null; } } catch (Exception e) { e.printStackTrace(); throw new CorbaSqlException(e.toString()); } } protected void disconnect() { try { if (m_results != null) m_results.close(); if (m_stmt != null) m_stmt.close(); if (m_conn != null) m_conn.close(); } catch (Exception e) { e.printStackTrace(); } m_results = null; m_stmt = null; m_conn = null; } public static void main(String args[]) { try { // create and initialize the ORB Properties props = new Properties(); props.put("org.omg.CORBA.ORBInitialPort", "1250"); ORB orb = ORB.init(args, props); // create server and register it with the ORB MarketDataServer server = new MarketDataServer(); orb.connect(server); // get the root naming context org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); NamingContext ncRef = NamingContextHelper.narrow(objRef); // bind the Object Reference in Naming NameComponent nc = new NameComponent("MarketData", ""); NameComponent path[] = {nc}; ncRef.rebind(path, server); // wait for invocations from clients java.lang.Object sync = new java.lang.Object(); synchronized (sync) { System.out.println("Waiting for client connection"); sync.wait(); } } catch (Exception e) { e.printStackTrace(); } } }
The
MarketData interface, according to the IDL file described above, defines two methods: getFirstData() and getNextData(). Class MarketDataServer extends the compiler-generated _MarketDataImplBase class (not listed here), which, in turn, implements MarketData interface.The
MarketDataServer constructor loads the JDBC-ODBC bridge driver used to retrieve data from the database.The
getFirstData() method creates an SQL query and returns the first data record (in the form of a DataUnit) or null if no data is available. As a first step, typical for any network application, user authentication is checked. If the user's name or password does not match the required values, a LoginException is thrown.
Note: In a more realistic CORBA application we would use a more sophisticated mechanism of user authentication than hard-coding a single username and password! Encryption is an increasingly important topic for addressing the security of online transactions.
getFirstData()
then creates an SQL query to retrieve data corresponding to a certain date, establishes a connection to the driver, creates an SQL statement, and executes a query. If at least one record is retrieved by that query, a new DataUnitImpl object is created and returned. Otherwise, the method returns null. Note that if any exceptions occur, they will be caught and thrown as CorbaSqlExceptions.The
getNextData() method will most likely be called repeatedly to retrieve remaining data records fetched by an SQL query. If the result set is currently in use and a new record can be retrieved, a new DataUnitImpl object is created and returned. Otherwise this method calls disconnect() and returns null. Similar to getFirstData(), all exceptions are caught and thrown as CorbaSqlExceptions.The
disconnect() method closes an SQL connection and sets all related references to null.The
main() method creates an instance of this server and registers it with an ORB. Let's discuss it step-by-step:
A call to the
A new
MarketDataServer instance is created and connected to our ORB. At this point the ORB server can start receiving remote invocations.The CORBA naming service is resolved, and a reference to the
NamingContext instance is retrieved using the narrow() method (different implementations of this method serve as the equivalent of class casting).A
NameComponent object is created to encapsulate the name of this service: "MarketData". An array of NameComponents can hold a fully specified path to an object on any file or disk system. In our case this path contains only one element. The rebind() method binds this path and the server object, so a call to our server may be resolved by CORBA.Finally, a
java.lang.Object is created and access to it is synchronized to wait for a server connection.
Note: These steps are similar to the typical procedure used to establish an RMI connection.
Note: Because of name conflict with
org.omg.CORBA.Object, all references to java.lang.Object must be explicit in Java 2 CORBA-related code.
26.5 Creating a CORBA client
Its finally time to take a look at how we can modify our stocks table application from chapter 18 to retrieve data from the CORBA server.
Figure 26.1 Login dialog used to connect to CORBA-enabled remote server.
<<file figure26-1.gif>>
The Code: StocksTable.java
see \Chapter26\1
import java.awt.*; import java.awt.event.*; import java.util.*; import java.io.*; import java.text.*; import javax.swing.*; import javax.swing.border.*; import javax.swing.event.*; import javax.swing.table.*; import dl.*; import MarketDataApp.*; import org.omg.CosNaming.*; import org.omg.CosNaming.NamingContextPackage.*; import org.omg.CORBA.*; public class StocksTable extends JFrame { protected JTable m_table; protected StockTableData m_data; protected JLabel m_title; private String m_name = "CORBA"; private String m_password = "Swing"; protected MarketData m_server = null; // Remote server ref. public StocksTable() { super("Swing Table [CORBA]"); setSize(770, 300); getContentPane().setLayout(new BorderLayout()); // Unchanged code WindowListener wndCloser = new WindowAdapter() { public void windowOpened(WindowEvent e) { LoginDialog dlg = new LoginDialog(StocksTable.this); dlg.show(); if (!dlg.getOkFlag()) System.exit(0); // Connect to the server try { // create and initialize the ORB String[] args = new String[] { "-ORBInitialPort", "1250", "ORBInitialHost", "localhost" }; ORB orb = ORB.init(args, null); // get the root naming context org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); NamingContext ncRef = NamingContextHelper.narrow(objRef); // resolve the Object Reference in Naming NameComponent nc = new NameComponent("MarketData", ""); NameComponent path[] = {nc}; m_server = MarketDataHelper.narrow(ncRef.resolve(path)); } catch (Exception ex) { ex.printStackTrace(System.out); System.exit(0); } retrieveData(); } public void windowClosing(WindowEvent e) { System.exit(0); } }; addWindowListener(wndCloser); setVisible(true); } // Unchanged code public void retrieveData() { SimpleDateFormat frm = new SimpleDateFormat("MM/dd/yyyy"); String currentDate = (m_data.m_date == null ? "7/22/1998" : frm.format(m_data.m_date)); String result = (String)JOptionPane.showInputDialog(this, "Please enter date in form mm/dd/yyyy:", "Input", JOptionPane.INFORMATION_MESSAGE, null, null, currentDate); if (result==null) return; // Unchanged code } // Unchanged code class LoginDialog extends JDialog { protected JTextField m_nameTxt; protected JPasswordField m_passTxt; protected boolean m_okFlag = false; public LoginDialog(Frame owner) { super(owner, "Login", true); JPanel p = new JPanel(new DialogLayout2()); p.add(new JLabel("Name:")); m_nameTxt = new JTextField(m_name, 20); p.add(m_nameTxt); p.add(new JLabel("Password:")); m_passTxt = new JPasswordField(m_password); p.add(m_passTxt); p.add(new DialogSeparator()); JButton btOK = new JButton("OK"); ActionListener lst = new ActionListener() { public void actionPerformed(ActionEvent e) { m_name = m_nameTxt.getText(); m_password = new String(m_passTxt.getPassword()); m_okFlag = true; dispose(); } }; btOK.addActionListener(lst); p.add(btOK); JButton btCancel = new JButton("Cancel"); lst = new ActionListener() { public void actionPerformed(ActionEvent e) { dispose(); } }; btCancel.addActionListener(lst); p.add(btCancel); p.setBorder(new EmptyBorder(10, 10, 10, 10)); getContentPane().add(p); pack(); setResizable(false); Dimension d1 = getSize(); Dimension d2 = owner.getSize(); int x = Math.max((d2.width-d1.width)/2, 0); int y = Math.max((d2.height-d1.height)/2, 0); setBounds(x, y, d1.width, d1.height); } public boolean getOkFlag() { return m_okFlag; } } public static void main(String argv[]) { new StocksTable(); } } class ColoredTableCellRenderer extends DefaultTableCellRenderer { public void setValue(java.lang.Object value) { // Unchanged code } } class StockData { // Unchanged code public StockData(DataUnit unit) { this(unit.getSymbol(), unit.getName(), unit.getLast(), unit.getOpen(), unit.getChange(), unit.getChangePr(), unit.getVolume()); } } class StockTableData extends AbstractTableModel { // Unchanged code public StockTableData() { m_frm = new SimpleDateFormat("MM/dd/yyyy"); m_vector = new Vector(); //setDefaultData(); No longer used } public int retrieveData(java.util.Date date, String name, String password, MarketData server) { GregorianCalendar calendar = new GregorianCalendar(); calendar.setTime(date); int month = calendar.get(Calendar.MONTH)+1; int day = calendar.get(Calendar.DAY_OF_MONTH); int year = calendar.get(Calendar.YEAR); try { DataUnit unit = server.getFirstData(name, password, year, month, day); boolean hasData = false; while (unit != null) { if (!hasData) { m_vector.removeAllElements(); hasData = true; } m_vector.addElement(new StockData(unit)); unit = server.getNextData(); } if (!hasData) // We've got nothing return 1; } catch (Exception e) { e.printStackTrace(); System.err.println("Load data error: "+e.getMessage()); return -1; } m_date = date; Collections.sort(m_vector, new StockComparator(m_sortCol, m_sortAsc)); return 0; } }
// Unchanged code
Understanding the Code
Class StocksTable
Compared to the final stocks table example in chapter 18, we now import two custom packages (
dl and MarketDataApp), and three CORBA-related packages.New instance variables:
String m_name
: user login name for authentication.String m_password
: user password for authentication.MarketData m_server
: a reference to the remote server obtained from CORBA.The
WindowAdapter used in this frame class now receives a new method, windowOpened(), which will be invoked when after the frame has been created and dispalyed on the screen (i.e. when setVisible(true) is called). We use this method to display a login dialog (see below). Then we connect to the CORBA server by following the same procedure as described above. Note that an additional property, ORBInitialHost, is passes to the ORB.init() method. This property determines an initial host address which will be checked for the required CORBA server (note that if both a CORBA server and client reside on the same! machine, we can omit this property).A reference to a
MarketData is retrieved and stored in our m_server variable. Then the retrieveData() method is called to populate our table with the initial data fetched using the CORBA server. The only change made to this method is that it now sets the initial string for data input before calling JOptionPane.showInputDialog(). The rest of the job is delegated to StockTableData.retrieveData() method, as we did in the chapter 18 example.Class LoginDialog
This inner class represents a custom modal dialog which allows input of a user name and password for authentication. Our custom
DialogLayout2 layout manager (discussed in chapter 4) simplifies the creation of this dialog, which is shown in figure 26.1.If the "OK" button is pressed, the username and password are retrieved and stored in class variables. It also sets the
m_okFlag to true. This flag can be retrieved with the getOkFlag() method and indicates how this dialog was closed (i.e. whether "OK" or "Cancel" was pressed).Class StockData
This class now receives a new constructor which creates a new
StockData object from a DataUnit instance discussed above.Class StockTableData
This class no longer uses the
setDefaultData() method. All data must be retrieved from the remote server. The retrieveData() method now receives three new parameters: user name, password, and a reference to the remote server. This method no longer uses SQL queries to retrieve data, but instead calls the getFirstData() method on the remote server (as we discussed in the last section). Then it repeatedly calls getNextData() until no more data is available. Retrieved data objects are encapsulated in StockData instances and stored in our m_vector collection.Running the code
At this point you can compile all code and run the Java 2 name server:
tnameserv -ORBInitialPort 1250
Then run our CORBA server:
java MarketDataApp.MarketDataServer
...and wait until it displays a message "Waiting for client connection" in the console. In another session, run our CORBA client:
java StocksTable
Enter the user name and password, "CORBA" and "Swing" respectively (they are displayed by default). Select a date for retrieval of corresponding stock data from the remote CORBA server. This data will be retrieved and displayed in the table.
Note: Don't forget to verify that your database is correctly listed as an ODBC data source. If your server runs across a network, specify it's host while creating ORB object.
Note: We can improve this application’s responsiveness during data retrieval by wrapping the associated code in threads. An even more professional application might implement progress bars to let the user know how far along the procedure is.