RMI: Distributed Java For the Rest Of Us - by Rick Proctor

By: Rick Proctor

Abstract: A real-world example of using socket connections, RMI, and CachedRowSets for disributed processing in a non-J2EE environment.

RMI: Distributed Java For the Rest Of Us

J2EE seems to be the part of Java that receives the most press these days. When you think J2EE, you tend to think in terms of costly…but cool…application servers and large enterprises. But, what about the rest of us? What about the small and medium sized businesses that need to do distributed processing and either can't afford or don't want to spend the capital on brand-name app servers? You know the type: guys like me who are looking for "good enough" technologies Yes, we could use JBoss, and probably will at some point, but for right now plain old Tomcat works great. It's is easy to set up, and it meets our needs. "But Tomcat doesn't support EJB...and we need to do distributed processing," you say. Well I say, "Fine. Let's use RMI to accomplish our distributed tasks." In this article, I'll discuss a real-world example using RMI and Tomcat to run a distributed application. The examples referenced in this article were developed using Jbuilder 5 and Jbuilder 9, Sun's J2SE 1.4, Tomcat 4.127, Microsoft Access, Red Hat Linux 7.3, and Microsoft Windows NT 4.0.

A Simple Client/Server Need

A few years ago a friend asked me to provide some technology consulting to a company he owns. The company provides psychological evaluations for pre-employment screening and job-role fit. My friend asked me to help them take their evaluation testing service to the web. The first hurdle was to convert their current test administration software from CBASIC running on DOS…yes I said DOS…to Windows Visual Basic 6.0. I contracted a VB programmer who worked with the original developer in converting the code. They ended up with a Windows based administration module utilizing a Microsoft Access database and TCP/IP sockets for evaluation processing.

With the administration module in place, we began reviewing the underlying technologies needed for a website front-end. Like many small enterprises, the company couldn't afford much in the way of web servers and application servers. So, it didn't take long to see the value in using JSP and Tomcat.

The first iteration of their web-based system consisted of running Tomcat 3.3 on the same Windows NT 4.0 server handling the test administration. A simple JavaBean was created to handle socket connections between the JSP client and the VB server program.

Using Sockets in Java

As you may already know, sockets are an abstraction for network connections. Java provides some relatively simple interfaces and classes for utilizing sockets. Java's socket interfaces and class definitions are found in the java.net package. The following example illustrates using a socket to connect to a server:


  public void setprocessEval(boolean process) {

    try {

      // establish a socket connection
      SocketPermission p = new SocketPermission(ipAddress + ":" + port + ","connect,resolve");
      InetAddress address = InetAddress.getByName(ipAddress);

      // receive a socket 
      Socket socket = new Socket(address, port);

      // establish an OutputStream for writing data
      OutputStream os = socket.getOutputStream();
      OutputStreamWriter osw = new OutputStreamWriter(os);
      PrintWriter pw = new PrintWriter(osw);

      // write the information
      pw.println(evaluationString.substring(0, evaluationString.length()));
      pw.flush();

      // establish an InputStream for reading the socket
      InputStream is = socket.getInputStream();
      InputStreamReader isr = new InputStreamReader(is);
      int c;
      StringBuffer instr = new StringBuffer();

      // read the data coming across the socket
      while ((c=isr.read()) != -1) instr.append((char) c);
      processEval = true;
        }

    catch (IOException f) {
      processEval = false;
      }
    catch (Exception g) {
      processEval = false;
      }
  }

The basic steps for using sockets are: establish a connection with a server, receive a socket from the server, establish a stream for writing data, write the data, and receive a response from the host system. As far as the port is concerned, it is typically assigned by the server. The client needs to know the port that the server is listening on. Unlike some other languages, VB for example, there is no need to keep querying the host to determine whether all the information has been sent/received. The underlying Java classes handle this for you.

Back to The System

The VB administration program we talked about earlier processes evaluation input, creates PDF evaluation reports, and stores the results in a MS Access database. To view the results on-line, I built a secure area with then JSP site. I used the JDBC-ODBC bridge provided with Java,(sun.jdbc.odbc.JdbcOdbcDriver), to access the database. While it's not necessarily the most efficient way to access ODBC databases, especially when there are several good commercial drivers available, it does work...and remember this particular company didn't have much money to invest in technology.

The Windows NT 4.0 server worked well for a year or so. But as the number of customers using the website grew, it became apparent that the application server needed to be separated from the administration server. After some technology discussions with the company's board, a decision was made to pursue running the entire process on Linux. I set up a Linux server for them and began the process of moving the website to Linux.

An Introduction to Remote Method Invocation

Obviously, moving JSP from Windows to Linux is not a problem. The only challenge I had was how to access the MS Access databases. I didn't have an ODBC administrator on the Linux server…and really didn't want to set one up because the long-term plan is to move the company away from Access. After looking over the possibilities, I decided to use RMI (Remote Method Invocation) to handle data access between the servers.

For the remainder of this article I'm going to show basic RMI concepts and some code examples. For a more complete discussion of RMI you may want refer Sun's white paper "Java Remote Method Invocation - Distributed Computing for Java http://java.sun.com/marketing/collateral/javarmi.html and Builder's tutorial "Getting Started with RMI".

Basically, the RMI model works as follows:

In the above example, class1 on the client need to call class2.method() from the server. Using RMI, the client obtains an instance of class2_stub class. The stub class (class2_stub) is automatically generated from the server class (class2Impl). You use the rmic (RMI Compiler) command to create a stub class (y the way JBuilder does a great job of managing this for you). Internally the stub class creates a socket connection to the server or it can use a pre-existing connection. It marshalls the information associated with method call and sends this information to the server. The server demarshalls the data and makes the method call on the actual server object. The server marshalls the return value and sends it back to the client. As complicated as all this sounds, Java is doing most of the work for you.

With RMI, you can pass values to and from remote methods either by reference or by value depending on need. Values that are passed by reference extend the "Remote" interface. You extend the Serializable interface to pass objects by value. In the following examples, I'll be extending the Remote interface.

An Introduction to CachedRowSets

After determining that I would use RMI to communicate between the servers, I needed to find a way to return a RowSet from a remote method. RowSets are useful for returning scrollable result sets from a table. A RowSet can either be connected or disconnected. To use RowSets you'll need to download the sun.jdbc.rowset package from Sun.

If you are familiar with JDBC, you know that to work with a database you'll need to do the following:

· Load and register a driver
· Establish a connection
· Create a statement
· Then do something like executeQuery().


Once you've done this, you then have an open connection to the database until you close it. All of this works well if your program can establish a direct connection to the database. But, what happens when you want to pass the RowSet as remote object? You could parse the data and pass it as a vector or array of objects or such. Or, you could use the CachedRowSet. CachedRowSet was introduced as part of JDBC 2.0.

CachedRowSets are essentially the same as RowSets but they create a cache of the data that can be passed around remotely. Think of CachedRowSets as disconnected ResultSets. Big caution here, if you are planning on passing CachedRowSets you'll need to think about how you want to pass data around because data = network traffic = bandwidth...something you want to assess before actually doing it.

Getting back to my example, I now had a solution for accessing the MS Access data from my Linux server. Basically I created a simple RMI server program that receives an input string containing the SQL statement from the client and returns a CachedRowSet from the server.

RMI Code Examples

The Client JavaServer Page

<%@ page contentType="text/html; charset=iso-8859-1" language="java" import="java.sql.*, java.rmi.*, javax.sql.*" %>

//CachedRowSets are beans and are instantiated as such
<jsp:useBean id="Results" class="sun.jdbc.rowset.CachedRowSet" scope="session">
</jsp:useBean>
<%

String sqlStmt = "select something ";
String serverIP = "some ip address "; 

//establish a RMI connection with the server
getData gData = (getData)Naming.lookup("rmi://" + serverIp + "/getData");

RowSet Recordset1 = gData.callSql(sqlStmt);

While (!Recordset1.eof()){
Recordset1.next();

	String someField = Recordset1.getString("someColumn");

	/* some html and JSP stuff would follow
	...

	*/

}
Recordset1.close();

%>

The above example illustrates the basics of using a JavaServer page as an RMI client that receives a CachedRowSet. While I've not included all of the HTML components, this should give you an idea of CachedRowSets work. Notice that I've treated the CachedRowSet like any other javabean (ie. using the jsp:usebean tags). Pay attention also to the RMI connection. Following normal Java conventions, the JSP instantiates the getData object. In order to create a new instance of getData called gData you have to locate the getData interface and getData_stub files in the classpath of the JSP. Once instantiated, the client referenced the callSql() method passing the appropriate SQL statement. When the RowSet is returned, the client JSP can then scroll through the data, retrieving the necessary information.

The Server Interface:


import java.sql.*;
import javax.sql.*;

public interface getData  extends java.rmi.Remote  {
  public RowSet callSql(String instring)
    throws java.rmi.RemoteException;
}

Notice that the getData interface extends java.rmi.Remote. It defines one public method callSql(), and because it extended java.rmi.Remote the interface must throw a java.rmi.RemoteException.

The Server Implementation:


import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
import java.sql.*;
import javax.sql.*;
import sun.jdbc.rowset.*;

public class getDataImpl extends UnicastRemoteObject implements getData {
  static Connection con;
  static Statement stmt;
  static String Connection;
  static String Driver;
  static String User;
  static String Password;
  static ResultSet rs;
  static String hostIp;

  public getDataImpl() throws RemoteException {
    super();
  }

  public RowSet callSql(String s) throws RemoteException {

  hostIp = "some IP address";
  Driver = "sun.jdbc.odbc.JdbcOdbcDriver";
  Connection = "jdbc:odbc:someDatabase";
  User = "";
  Password = "";

  try {
     CachedRowSet rset = new CachedRowSet();
    // Load and register the driver
     Class.forName(Driver);
    // Create the connection
     con = DriverManager.getConnection (Connection, User, Password);
    // Create the Statement
    stmt = con.createStatement();

    // put the results of the executeQuery into the ResultSet object rs    
    rs = stmt.executeQuery(s);

    // populate the CachedRowSet object rset with the data from the ResultSet object rs
    rset.populate(rs);

    // return to the client the CachedRowSet object rset
    return(rset);
     }
      catch (ClassNotFoundException e) {
        System.out.println(e.getMessage());
      }
      catch (SQLException e) {
        System.out.println("SQL Msg1:" + e);
      }
     return(null);
  }
  public static void main(String[] arg) {
    try {
      getDataImpl gData = new getDataImpl();
      Naming.rebind("rmi://"+ hostIp + "/getData",gData);
      }
    catch(Exception e) {
      System.out.println("getDataImpl: " + e);
    }
  }
}

In getDataImpl, notice that under the main() method there is a Naming.Rebind() method call. This method registers the RMI server program with the RMI Registry. If you look back at the client code, you'll see that it calls the Naming.lookup() method using the name "getData" that was registered by the server program. The Naming.lookup() method invokes the RMI Registry on the server (as indicated by the IP and/or DNS) and looks to see if "getData" class is registered. Assuming a successful lookup, the client can now make method calls. You'll notice that there is only one public method call in the getDataImpl program…callSql(). Looking closely at the line the program, you can see that the getData class expects a String and returns a RowSet RecordSet1. The returned RowSet can then be queried using familiar methods: .next(), .getString(), etc.

In Summary

I hope that the above examples have given you a taste of providing small scale distributed processing with RMI. The investment you make in RMI will not be lost if you decide to move to J2EE and EJB. RMI is a part of the J2EE spec and understanding how it works will help when you decide to make the leap. The next step for my friend's company is to convert his VB system to a Java system and switch from MS Access to a more robust database. Now at least, he has a foundation and some breathing room to grow as his business does.


Server Response from: ETNASC03