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 DataUnit interface holds data for a single row in our stocks table.

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 ORB.init() static method creates an ORB. The ORB constructor takes two parameters: a String array of application parameters, and a Properties instance created from within the application. Note that we have explicitly set the ORBInitialPort property to 1250, specifying the port for our ORB to run on.

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.