by Bryan Morgan

Java 1.2 extends Java’s distributed object capabilities

how-to
Apr 1, 199828 mins

Find out what RMI and Java IDL, Java 1.2's seemingly similar distributed object technologies, have to offer you

Welcome to JavaWorld‘s new bimonthly Distributed Objects column! The purpose of this column is to discuss distributed object technology and products as they apply to Java developers. Over the course of the next 12 months, we’ll be focusing on such technologies as RMI, CORBA, and DCOM, as well as on tool and product offerings that will be of interest to enterprise-scale Java developers. As is the case with all JavaWorld columns, you’re encouraged to contact me if you have topics you would like to see addressed in this forum.

If the whole idea of distributed object computing is new to you, I suggest you take some time to review two articles on this topic previously published in JavaWorld. The first of these articles, “CORBA meets Java,” provides an introduction to CORBA development in Java. The second article, “Applied CORBA: Integrating legacy code with the Web,” discusses the use of CORBA to allow Java developers to access legacy code as remote objects. The latter article deals specifically with calling remote objects written in C++ from a Java applet or application.

Without further ado, let’s begin what hopefully will be an informative and enlightening undertaking!

Surveying the field

With the release of the Java 1.2 platform, portions of the Java Enterprise specification begin to make the transition from documents and specifications (that is, vaporware) to actual working code. Although the Java 1.1 release gave Java developers the ability to create distributed pure-Java applications from scratch, Java 1.2 extends these capabilities with Java IDL. To quote the JDK 1.2 documentation:

Java IDL adds CORBA (Common Object Request Broker Architecture) capability to Java, providing standards-based interoperability and connectivity. Java IDL enables distributed Web-enabled Java applications to transparently invoke operations on remote network services using the industry standard OMG IDL (Object Management Group Interface Definition Language) and IIOP (Internet Inter-ORB Protocol) defined by the Object Management Group. Runtime components include a fully-compliant Java ORB for distributed computing using IIOP communication.

In other words, Java 1.2 ships with a 100% Pure Java object request broker (ORB) that can interoperate with any CORBA 2.0-compliant ORB, including IONA Technologies Orbix, Visigenic Software’s VisiBroker, and IBM’s ComponentBroker. This addition eliminates the requirement for licensing and distributing third-party Java ORBs and will greatly improve download times for CORBA applets as Java 1.2 Web browsers become available. Unfortunately, the major Web browsers (Microsoft Internet Explorer 4.0 and Netscape Navigator 4.0) continue to struggle to provide implementations of Java 1.1. Therefore, it may be late 1998 before valid Java 1.2 browser implementations appear on the market.

More importantly, with IDL JavaSoft’s “enterprise” vision for Java begins to take shape, because Java IDL will allow Java developers to take advantage of CORBA transaction, naming, and messaging services. In fact, Java IDL actually ships with a CORBA-compliant naming service known as the Java IDL naming service, which allows objects to be accessed on servers using a standardized set of interfaces. Despite these advances, confusion still abounds among three camps of Java developers:

Camp 1: CORBA veterans (been there, done that!)

This first camp of developers have been developing CORBA applications for some time. When Java came along, they immediately saw its promise and some even began retrofitting existing CORBA applications with Java clients and Java ORBs. To these hearty souls who have braved the changing winds of time and managed to successfully build and field applications using CORBA as their architecture, all of the promised pieces are finally falling into place. Highly visible vendors such as Netscape and Oracle are adding CORBA components to their product offerings, and Java promises to take CORBA to places once thought impossible. With all of these positive goings on, RMI becomes a purely extraneous technology (almost a distraction) that should be ignored in hopes that it will eventually die from neglect.

Camp 2: Java purists (Java, Java everywhere!)

Members of this camp believe that all new applications should be completely written in Java and, for good measure, most old applications should be rewritten for purity’s sake. CORBA sounds great in a theoretical sense, but who cares about legacy code integration when all components are written in Java? JavaSoft promises several important Java services (transaction, directory, among others) over the coming years so, in time, the Java platform will match CORBA feature for feature. Because the client and server are written in Java, there’s no need for any messy IDL code and all objects are first-class Java citizens.

Camp 3: Everyone else (Hey… I just want something that works!)

Developers in this camp have never built a production application using distributed objects but can see the advantage to this architecture in certain situations. For someone starting an application completely from scratch (no legacy code required), a 100 percent Java solution will probably be on the short list of options. This type of development project will require analysis of the capabilities of Java RMI and Java IDL and decisions based on need.

A brief technology comparison

I won’t even attempt to estimate the number of developers that fall into each camp, but I will venture a guess that 90 percent of you fall into Camp 3 and simply want a robust technology that actually does what it says it will do. With just about all comparable technologies, you can rest assured that each technology will have some advantages and disadvantages when compared with the competition. This is especially true when comparing RMI and CORBA. (Note that in this comparison, CORBA and Java IDL are used somewhat interchangeably. This is due to the fact that CORBA is an open specification “owned” by the OMG, and Java IDL is an implementation of this specification.)

To some, RMI’s biggest advantage is the fact that it was designed from the ground up to be an all-Java solution. This means that building RMI applications is quite simple, and all remote objects have the same features as local objects (that is, you can typecast and subclass them). Of course, RMI’s biggest advantage is also its biggest disadvantage: it was designed from the ground up to be an all-Java solution — a very exclusive club!

CORBA, meanwhile, benefits from the fact that it is a language-independent solution, but that adds significant complexity to the development cycle and also precludes using Java’s garbage collection features and RMI’s pass-by-copy parameter passing. While there is currently a formal request for proposals (RFP) out on CORBA pass-by-copy, it will never be at a level supported by RMI, due to the complexities of morphing objects from one language to another.

The following table attempts to summarize many of the features supported by the two architectures. Following this table, we will begin development of two simple Java applications using RMI and Java IDL. Remember, Java IDL is simply an implementation (albeit a subset) of the CORBA specification, therefore Java IDL and CORBA can be compared interchangeably in many areas.

Comparison Of RMI and CORBA Features
CapabilityCORBARMI
Server CallbacksYesYes
Pass-By-ValueNoYes
Dynamic DiscoveryYesNo
Dynamic InvocationsYesYes
Multiple Transport SupportYesYes
URL NamingNo (ORB-dependent)Yes
Firewall ProxyNo (ORB-dependent)Yes
Language-IndependentYesNo
Language-Neutral Wire ProtocolYes (via IIOP)No (Future IIOP?!?)
Persistent NamingYesNo
Wire-level SecurityYes (via CORBASecurity)Yes (via SSL)
Wire-level TransactionYes (via CORBA OTS)Yes (via JTS)

MortgageCalc 1.0 — one app, two technologies

To highlight the similarities and differences between RMI and Java IDL, I deemed it best to build two versions of the same application — MortgageCalc 1.0 — one for each of the technologies (see

Resources

for the complete source for these two apps). Each application will be comprised of a client and server component used to calculate and display mortgage payments based on a number of initial conditions (length of loan, interest rates, taxes, insurance, and down-payment amount). Because the number-crunching portion of these two applications will be identical, we can encapsulate that calculation code within a single Java class to be shared by both. This will allow us to focus on the specific steps involved in building an application using RMI and Java IDL.

Before we begin, I’ll briefly explain the formula we’ll be using to perform the calculations. Note: These calculations use the mortgage formula for U.S. mortgages. Other countries, such as Canada, compound interest semi-annually, resulting in a different calculation.

The monthly payment (PMT) represents the principal plus the interest you owe the bank each month. In addition to this payment, your total payment (TOTPMT) will also include city/county taxes (TAX) and home-owner’s insurance (INS). In other words, your monthly payment can be represented by the following formula:

TOTPMT = PMT + TAX + INS

The TAX and INS values are simple to calculate (TotalAmounts/12), but PMT proves to be more difficult. Without going into detail, the formula used to calculate this value is as follows:

PMT = BAL * (INT / (1 - (1 + INT) ** -MON))

BAL is the initial amount of the loan, INT is the monthly interest, and MON is the number of months over which the loan is amortized (Years * 12).

The PaymentCalc class

To avoid duplicating the calculation code in both our RMI and Java IDL examples, we will build a simple Java class,

PaymentCalc

, that can be reused in both applications. If these financial applications were to grow in complexity, we could easily extend the

PaymentCalc

class without continually duplicating changes to two sets of code. For now,

PaymentCalc

contains two methods —

PI()

and

PITI()

. The

PI()

method calculates a payment consisting of principal and interest, while the

PITI()

method calculates a payment consisting of principal, interest, taxes, and insurance.

class PaymentCalc { /* PI calculates only the principal and interest payment */ double PI(double Balance, double AnnualInterestRate, int YearsLength) { double BAL = Balance; double INT = (AnnualInterestRate / 12); double MON = (YearsLength * 12);

double PMT = BAL * (INT / (1 - java.lang.Math.pow(1 + INT, -MON)));

return PMT; }

/* PITI calculates the total payment, including insurance and taxes */ double PITI(double Balance, double AnnualInterestRate, int YearsLength, int AnnualInsurance, int AnnualTaxes) { double BAL = Balance; double INT = (AnnualInterestRate / 12); double MON = (YearsLength * 12);

double PMT = BAL * (INT / (1 - java.lang.Math.pow(1 + INT, -MON))); double INS = (AnnualInsurance / 12); double TAX = (AnnualTaxes / 12); double TOTPMT = PMT + INS + TAX;

return TOTPMT; } }

The server portions of the RMI and Java IDL applications will utilize the PaymentCalc class to perform calculations for a variety of term lengths and principals and will return an array of payment options to the client.

The RMI-based MortgageCalc app

Developing an RMI application consists of the following steps:

  1. Define a remote interface
  2. Implement the remote interface by building an implementation class (server app)
  3. Generate client stubs and server skeletons using rmic
  4. Start the RMI registry and register remote objects
  5. Build the client application
  6. Start client application and connect to the server

Note that these steps look vaguely similar to those required when we built our CORBA examples several months ago. The major difference lies in the first step. Using RMI, we can define the remote interface using the Java programming language as opposed to IDL.

Step 1: Define a remote interface

The first step requires us to define the operations we would like to be performed by our server. An extremely simple application could calculate a single payment based on principal, interest, and length, but we’ll take this a step further. Instead of calculating a single payment, let’s allow our server to calculate a variety of payments based on options such as amount of down-payment (5%, 10%, or 20%) and length of loan (15 year or 30 year). These options will allow us to examine the passing of data types from client and server. In the RMI case, we will return a Vector object to return a list of objects containing the calculated data.

The Calculate interface will be implemented by our server application.

public interface Calculate extends java.rmi.Remote
{
  public java.util.Vector calcPI(int HousePrice, 
                  double InterestRate) throws java.rmi.RemoteException;
  public java.util.Vector calcPITI(int HousePrice, double InterestRate, 
                  int Insurance, int Taxes) throws java.rmi.RemoteException;
}

The methods in this interface return a Vector object, which acts as a container for other objects. At this time, we’ll also define a class, ResultSet, to contain the data calculated by the interface’s methods. Keep in mind that a single call to one of Calculate‘s methods will return numerous ResultSet objects stored in a Vector. Also notice that the ResultSet class implements the java.io.Serializable interface. RMI requires that all items passed between local and remote objects implement the Serializable interface.

import java.io.*;

public class ResultSet implements Serializable { int Years; double DownPayment; double Balance; double InterestRate; double MonthlyPayment;

private void writeObject(ObjectOutputStream s) throws IOException { // save the data to a stream s.writeInt(Years); s.writeDouble(DownPayment); s.writeDouble(Balance); s.writeDouble(InterestRate); s.writeDouble(MonthlyPayment); }

private void readObject(ObjectInputStream s) throws IOException { Years = s.readInt(); DownPayment = s.readDouble(); Balance = s.readDouble(); InterestRate = s.readDouble(); MonthlyPayment = s.readDouble(); } }

Step 2: Implement the remote interface

Step number two actually consists of two smaller steps. The first requires us to implement the Calculate interface, while the second requires us to build a server application that instantiates the implementation class. This will make the class available on the server to any client applications that desire to make use of it. The majority of the code involved in this step is pretty straightforward. The only portion that may look unfamiliar is the call to Naming.rebind() within the CalculateImpl constructor. The Naming class is defined within java.rmi.Naming and is used to register the object with the Java RMI registry. (This process is similar in concept to the registration of a CORBA object with the basic object adaptor, BOA, within the ORB.)

We’ll begin with the source for the CalculateImpl implementation class:

import java.rmi.*; import java.rmi.server.UnicastRemoteObject;

public class CalculateImpl extends UnicastRemoteObject implements Calculate { private PaymentCalc calc;

public CalculateImpl(String name) throws RemoteException { super(); try { Naming.rebind(name, this); calc = new PaymentCalc(); } catch (Exception e) { System.out.println("Exception: " + e.getMessage()); e.printStackTrace(); } }

public java.util.Vector calcPI(int HousePrice, double InterestRate) throws java.rmi.RemoteException { java.util.Vector retObject = new java.util.Vector(6);

//5% down, 15 years ResultSet set1 = new ResultSet(); set1.Years = 15; set1.DownPayment = HousePrice * .05; set1.Balance = HousePrice * .95; set1.InterestRate = InterestRate; set1.MonthlyPayment = calc.PI(set1.Balance, set1.InterestRate, set1.Years); retObject.addElement(set1);

//5% down, 30 years ResultSet set2 = new ResultSet(); set2.Years = 30; set2.DownPayment = HousePrice * .05; set2.Balance = HousePrice * .95; set2.InterestRate = InterestRate; set2.MonthlyPayment = calc.PI(set2.Balance, set2.InterestRate, set2.Years); retObject.addElement(set2);

//10% down, 15 years ResultSet set3 = new ResultSet(); set3.Years = 15; set3.DownPayment = HousePrice * .10; set3.Balance = HousePrice * .90; set3.InterestRate = InterestRate; set3.MonthlyPayment = calc.PI(set3.Balance, set3.InterestRate, set3.Years); retObject.addElement(set3);

//10% down, 30 years ResultSet set4 = new ResultSet(); set4.Years = 30; set4.DownPayment = HousePrice * .10; set4.Balance = HousePrice * .90; set4.InterestRate = InterestRate; set4.MonthlyPayment = calc.PI(set4.Balance, set4.InterestRate, set4.Years); retObject.addElement(set4);

//20% down, 15 years ResultSet set5 = new ResultSet(); set5.Years = 15; set5.DownPayment = HousePrice * .20; set5.Balance = HousePrice * .80; set5.InterestRate = InterestRate; set5.MonthlyPayment = calc.PI(set5.Balance, set5.InterestRate, set5.Years); retObject.addElement(set5);

//20% down, 30 years ResultSet set6 = new ResultSet(); set6.Years = 30; set6.DownPayment = HousePrice * .20; set6.Balance = HousePrice * .80; set6.InterestRate = InterestRate; set6.MonthlyPayment = calc.PI(set6.Balance, set6.InterestRate, set6.Years); retObject.addElement(set6);

return retObject; }

public java.util.Vector calcPITI(int HousePrice, double InterestRate, int Insurance, int Taxes) throws java.rmi.RemoteException { java.util.Vector retObject = new java.util.Vector(6);

//5% down, 15 years ResultSet set1 = new ResultSet(); set1.Years = 15; set1.DownPayment = HousePrice * .05; set1.Balance = HousePrice * .95; set1.InterestRate = InterestRate; set1.MonthlyPayment = calc.PITI(set1.Balance, set1.InterestRate, set1.Years, Insurance, Taxes); retObject.addElement(set1);

//5% down, 30 years ResultSet set2 = new ResultSet(); set2.Years = 30; set2.DownPayment = HousePrice * .05; set2.Balance = HousePrice * .95; set2.InterestRate = InterestRate; set2.MonthlyPayment = calc.PITI(set2.Balance, set2.InterestRate, set2.Years, Insurance, Taxes); retObject.addElement(set2);

//10% down, 15 years ResultSet set3 = new ResultSet(); set3.Years = 15; set3.DownPayment = HousePrice * .10; set3.Balance = HousePrice * .90; set3.InterestRate = InterestRate; set3.MonthlyPayment = calc.PITI(set3.Balance, set3.InterestRate, set3.Years, Insurance, Taxes); retObject.addElement(set3);

//10% down, 30 years ResultSet set4 = new ResultSet(); set4.Years = 30; set4.DownPayment = HousePrice * .10; set4.Balance = HousePrice * .90; set4.InterestRate = InterestRate; set4.MonthlyPayment = calc.PITI(set4.Balance, set4.InterestRate, set4.Years, Insurance, Taxes); retObject.addElement(set4);

//20% down, 15 years ResultSet set5 = new ResultSet(); set5.Years = 15; set5.DownPayment = HousePrice * .20; set5.Balance = HousePrice * .80; set5.InterestRate = InterestRate; set5.MonthlyPayment = calc.PITI(set5.Balance, set5.InterestRate, set5.Years, Insurance, Taxes); retObject.addElement(set5);

//20% down, 30 years ResultSet set6 = new ResultSet(); set6.Years = 30; set6.DownPayment = HousePrice * .20; set6.Balance = HousePrice * .80; set6.InterestRate = InterestRate; set6.MonthlyPayment = calc.PITI(set6.Balance, set6.InterestRate, set6.Years, Insurance, Taxes); retObject.addElement(set6);

return retObject; } }

Now we simply need to create a basic server application, MortageCalcServer, that will instantiate our implementation class and register it with the RMI security manager. This can be done with a few lines of code as follows:

import java.rmi.*; import java.rmi.server.*;

public class MortgageCalcServer { public static void main(String args[]) { //Set up the server's security manager System.setSecurityManager(new RMISecurityManager());

try { CalculateImpl calculator = new CalculateImpl("JavaWorld MortgageCalc"); System.out.println("JavaWorld MortgageCalc Started!"); } catch (Exception e) { System.out.println("Exception: " + e.getMessage()); e.printStackTrace(); } } }

The RMISecurityManager object is used to ensure that classes are properly loaded. Once this has been set (it is required), we can simply instantiate the CalculateImpl object, which then registers the CalculateImpl object with the RMI registry.

Step 3: Generate stubs using rmic

The rmic tool, an application included with the JDK, takes as its input all remote object implementations (in the form of class files) you have created and outputs client stubs and server skeletons. A stub for a remote object is the client-side proxy for the remote object. This proxy is responsible for contacting the remote object, marshalling parameters to that object, and informing the remote reference layer that the call is complete. The skeleton, meanwhile, is a server-side entity that contains a method for dispatching calls to the actual remote object implementation. This skeleton is responsible for unmarshalling parameters, making the call to the actual implementation, and then marshalling return values.

When you run rmic, it will generate the CalculateImpl_Stub.class and CalculateImpl_Skel.class files:

rmic CalculateImpl

Step 4: Register objects

Similar to the OSAgent executable we ran to set up shop using the Visigenic VisiBroker ORB October’s “Corba meets Java” feature, the JDK 1.2 comes with a tool, rmiregistry, that acts to register all running object implementations on the local machine. You start this tool by executing the following command:

start rmiregistry

You register and run the server by executing the standard Java interpreter:

java MortgageCalcServer

Step 5: Build the client

You can start to relax now because the bulk of the complicated work is done. At this point, we have a functioning RMI server waiting for some lucky client to connect to it. Let’s build a client application that will do just that. Each RMI client application must first install an RMI security manager (just as we did on the RMI server) then locate the remote object it wishes to use. Once these tasks are done, the application can make use of the object just as if it were an object running within the program’s memory space. The MortgageCalcClient application takes in three parameters from the command line: ServerName, HousePrice, and InterestRate. Using these parameters, it calculates your mortgage payment and prints out rates for you.

import java.rmi.*; import java.rmi.registry.*; import java.rmi.server.*;

public class MortgageCalcClient { public static void main(String args[]) { if (args.length != 3) { System.out.println("Usage: java MortgageCalcClient ServerName HousePrice InterestRate"); System.out.println("Interest rate must be in decimal form!"); System.exit(0); }

String ServerName = args[0]; Integer HousePrice= new Integer(args[1]); Double InterestRate = new Double(args[2]);

//Install the security manager System.setSecurityManager(new RMISecurityManager());

try { Calculate calc = (Calculate)Naming.lookup("rmi://" + args[0] + "/" + "JavaWorld MortgageCalc");

java.util.Vector v = calc.calcPI(HousePrice.intValue(), InterestRate.doubleValue()); for (int i=0; i < 6; i++) { ResultSet s = (ResultSet)v.elementAt(i); System.out.println("Term of Loan=" + s.Years + " years"); System.out.println("Down Payment=$" + s.DownPayment); System.out.println("Interest Rate=" + s.InterestRate * 100 + "%"); System.out.println("Monthly Payment=$" + s.MonthlyPayment); System.out.println(); } } catch (Exception e) { System.err.println("System Exception" + e); } System.exit(0); } }

At the current time, the rmiregistry tool cannot look up auto-start objects stored on the server. It also does not support any dynamic discovery and dynamic invocation features. Java IDL, which is based on CORBA 2.0, does support these features and will be explored next.

The Java IDL-based MortgageCalc app

For this example we will use the Java IDL components included with the Java Development Kit 1.2 release. Because CORBA has been covered in detail in my earlier articles, we will focus less on CORBA theory and more on actually using the JDK 1.2 tools to build a Java-CORBA application. As in the other CORBA applets and/or applications we have developed, the following steps are required to build a complete application:

  1. Write a definition for each object using IDL
  2. Compile the IDL code using idltojava compiler to produce the server skeleton code and client stub code
  3. Implement the server object
  4. Build the server application
  5. Build the client application

The only significant complexity that differentiates this application from the Java/CORBA applications we developed in months past is our use of the Java IDL naming service. This difference is due to the fact that Java IDL currently does not include support for an interface repository. (Recall that in earlier articles using the Visigenic Visibroker ORB, we made use of the osagent utility to look up objects and retrieve them from the underlying interface repository). Instead, Java IDL supports a fully functional implementation of the CORBA Common Object Services (COS) naming service. (See the Resources section for more information on the COS naming service.) The inclusion of the naming service allows Java IDL and its naming service to interoperate with other CORBA 2.0-compliant ORBs through a set of OMG-defined interfaces.

A few words about the Java IDL naming service

The Java IDL naming service provides a tree-like directory for object references much like a file system provides a directory structure for files. The combination of an object reference and its given name is known as a name binding. Name bindings are stored under directory tree branches known as naming contexts. Therefore, an object reference is to a naming context what a file is to a directory on your computer’s file system.

To connect to the Java IDL naming service, a client application must know the hostname and port number that the service is currently running on. Once this connection has been made, the client requests a naming context and then resolves the object reference to a valid Java object for use in the application. Note that because this service is CORBA-compliant, it is not even necessary to use the naming service included with the JDK 1.2. In other words, a JDK 1.2 client application could be built using Java IDL, but could connect to the Visigenic naming service using the standardized naming service interfaces. This service is a very simple one (after all, it is only a first offering from JavaSoft). Object references are temporary and will be lost should the naming service be shut down. More complete implementations of the naming service are available from other CORBA software vendors (Expersoft, IONA, and Visigenic to name a few) and any applets or applications you write to work with the Java IDL naming service will work fine with other vendors’ services as well.

Because CORBA is an object-oriented architecture, it should come as no surprise to find that the naming service itself is accessed through a set of interfaces contained within the CosNaming module. The first of these interfaces, NamingContext, contains the methods necessary to perform name binding and resolution. The second interface, BindingIterator, contains methods used to iterate through the name bindings in order to locate a specific object. The Java IDL naming service will be used to locate an object reference to our Calculate server object in the following example.

Step 1: Defining the interface using IDL

Note: Java IDL is not an implementation of the OMG’s Interface Definition Language. As we have discussed, Java IDL is in fact a CORBA 2.0 ORB that uses IDL to define interfaces. This confusion could have been avoided by using anything but the letters “IDL” in the name of the ORB, but its makers chose to go with it anyway.

The process of defining our Calculate interface in IDL will differ slightly from the process of defining the corresponding interface in Java (see the source code above for the Java RMI Calculate interface). The reason for this is that with RMI we were able to return our mortgage payment results stored within a java.lang.Vector object. We also defined an individual Java class (ResultSet) to be stored within the Vector. IDL allows us to return groups of objects within an array or sequence. An examination of the Java-to-IDL mapping reveals that both of these IDL data types map to a Java array, so it doesn’t really matter which one we choose. For this example, we’ll go with the IDL sequence. The following code listing makes up the Calculate.IDL file and will be used to generate our stub and skeleton code in step 2.

struct ResultSet { long Years; double DownPayment; double Balance; double InterestRate; double MonthlyPayment; };

typedef sequence<ResultSet> MortgagePayments;

interface Calculate { MortgagePayments calcPI(in long HousePrice, in double InterestRate); MortgagePayments calcPITI(in long HousePrice, in double InterestRate, in long Insurance, in long Taxes); };

As you can see, the ResultSet struct is virtually identical to our ResultSet Java class defined in the RMI example. The Calculate interface is also very similar to the Calculate RMI interface except for the fact that this one returns a MortgagePayments sequence.

Step 2: Using idltojava

The idltojava compiler is the IDL compiler provided by JavaSoft. At the time of this writing, it is not included with the JDK 1.2 Beta 2 release, so you must download it separately (see Resources).

This compiler requires the use of the Microsoft Visual C++ preprocessor, therefore you should have Visual C++ installed and ready to go on your system. (Make sure you run the vcvars32 batch file to set up your environment correctly.) Once these items are set up, simply run the following command to produce the necessary client stubs and server skeletons:

idltojava Calculate.idl

This will produce 10 Java source files including the resultant Calculate.java, shown here:

public interface Calculate extends org.omg.CORBA.Object 
{
  ResultSet[] calcPI(int HousePrice, double InterestRate);
  ResultSet[] calcPITI(int HousePrice, double InterestRate, 
                       int Insurance, int Taxes);
}

As you can see, this interface is virtually identical to the RMI Calculate interface. The RMI version returns a Java Vector class, while the Java IDL version returns an array of ResultSet objects. Other than this, the two classes are alike.

Step 3: Implementing the server object

The Java IDL documentation refers to the two parts of your server application as a servant and a server. This is a good way of describing the relationship between your implementation class (we’ll reuse the name CalculateImpl for continuity’s sake) and the server application responsible for initializing the ORB instance, registering the servant, and handling client requests. Because the implementation details remain largely unchanged, the Java IDL CalculateImpl class will look very much like the RMI CalculateImpl class.

class CalculateImpl extends _CalculateImplBase { ResultSet[] calcPI(int HousePrice, double InterestRate) { ResultSet[] retObject = new ResultSet[6];

//5% down, 15 years ResultSet set1 = new ResultSet(); set1.Years = 15; set1.DownPayment = HousePrice * .05; set1.Balance = HousePrice * .95; set1.InterestRate = InterestRate; set1.MonthlyPayment = calc.PI(set1.Balance, set1.InterestRate, set1.Years); retObject[0] = set1;

//5% down, 30 years ResultSet set2 = new ResultSet(); set2.Years = 30; set2.DownPayment = HousePrice * .05; set2.Balance = HousePrice * .95; set2.InterestRate = InterestRate; set2.MonthlyPayment = calc.PI(set2.Balance, set2.InterestRate, set2.Years); retObject[1] = set2;

//10% down, 15 years ResultSet set3 = new ResultSet(); set3.Years = 15; set3.DownPayment = HousePrice * .10; set3.Balance = HousePrice * .90; set3.InterestRate = InterestRate; set3.MonthlyPayment = calc.PI(set3.Balance, set3.InterestRate, set3.Years); retObject[2] = set3;

//10% down, 30 years ResultSet set4 = new ResultSet(); set4.Years = 30; set4.DownPayment = HousePrice * .10; set4.Balance = HousePrice * .90; set4.InterestRate = InterestRate; set4.MonthlyPayment = calc.PI(set4.Balance, set4.InterestRate, set4.Years); retObject[3] = set4;

//20% down, 15 years ResultSet set5 = new ResultSet(); set5.Years = 15; set5.DownPayment = HousePrice * .20; set5.Balance = HousePrice * .80; set5.InterestRate = InterestRate; set5.MonthlyPayment = calc.PI(set5.Balance, set5.InterestRate, set5.Years); retObject[4] = set5;

//20% down, 30 years ResultSet set6 = new ResultSet(); set6.Years = 30; set6.DownPayment = HousePrice * .20; set6.Balance = HousePrice * .80; set6.InterestRate = InterestRate; set6.MonthlyPayment = calc.PI(set6.Balance, set6.InterestRate, set6.Years); retObject[5] = set6;

return retObject; }

ResultSet[] calcPITI(int HousePrice, double InterestRate, int Insurance, int Taxes) { ResultSet[] retObject = new ResultSet[6];

//5% down, 15 years ResultSet set1 = new ResultSet(); set1.Years = 15; set1.DownPayment = HousePrice * .05; set1.Balance = HousePrice * .95; set1.InterestRate = InterestRate; set1.MonthlyPayment = calc.PITI(set1.Balance, set1.InterestRate, set1.Years, Insurance, Taxes); retObject[0] = set1;

//5% down, 30 years ResultSet set2 = new ResultSet(); set2.Years = 30; set2.DownPayment = HousePrice * .05; set2.Balance = HousePrice * .95; set2.InterestRate = InterestRate; set2.MonthlyPayment = calc.PITI(set2.Balance, set2.InterestRate, set2.Years, Insurance, Taxes); retObject[1] = set2;

//10% down, 15 years ResultSet set3 = new ResultSet(); set3.Years = 15; set3.DownPayment = HousePrice * .10; set3.Balance = HousePrice * .90; set3.InterestRate = InterestRate; set3.MonthlyPayment = calc.PITI(set3.Balance, set3.InterestRate, set3.Years, Insurance, Taxes); retObject[2] = set3;

//10% down, 30 years ResultSet set4 = new ResultSet(); set4.Years = 30; set4.DownPayment = HousePrice * .10; set4.Balance = HousePrice * .90; set4.InterestRate = InterestRate; set4.MonthlyPayment = calc.PITI(set4.Balance, set4.InterestRate, set4.Years, Insurance, Taxes); retObject[3] = set4;

//20% down, 15 years ResultSet set5 = new ResultSet(); set5.Years = 15; set5.DownPayment = HousePrice * .20; set5.Balance = HousePrice * .80; set5.InterestRate = InterestRate; set5.MonthlyPayment = calc.PITI(set5.Balance, set5.InterestRate, set5.Years, Insurance, Taxes); retObject[4] = set5;

//20% down, 30 years ResultSet set6 = new ResultSet(); set6.Years = 30; set6.DownPayment = HousePrice * .20; set6.Balance = HousePrice * .80; set6.InterestRate = InterestRate; set6.MonthlyPayment = calc.PITI(set6.Balance, set6.InterestRate, set6.Years, Insurance, Taxes); retObject[6] = set6;

return retObject; } }

Step 4: Building the server application

As I mentioned earlier, the primary difference between this server application and the RMI MortgageCalcServer application is the fact that this one uses the Java IDL naming service. We do this by first retrieving the root naming context (similar to retrieving the root directory on a file system), then by adding a NameComponent to it to bind our calculator object. Once this is done, the application simply goes into a “wait” mode in order to handle client requests.

import org.omg.CosNaming.*; import org.omg.CosNaming.NamingContextPackage.*; import org.omg.CORBA.*;

public class MortgageCalcServer { public static void main(String args[]) { try { // create and initialize the ORB ORB orb = ORB.init(args, null);�?

// create servant and register it with the ORB CalculateImpl calculator = new CalculateImpl(); orb.connect(calculator);�?

// 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("JavaWorld MortgageCalc", ""); NameComponent path[] = {nc}; ncRef.rebind(path, calculator);�?

// wait for invocations from clients java.lang.Object sync = new java.lang.Object(); synchronized (sync) { sync.wait(); }�? } catch (Exception e) { System.err.println("ERROR: " + e); e.printStackTrace(System.out); } } }

Step 5: Building the client application

The client application (MortgageCalcClient) initializes the ORB, resolves the object reference using the naming service, then performs the same operations as the RMI client application.

import org.omg.CosNaming.*; import org.omg.CORBA.*;

public class MortgageCalcClient { public static void main(String args[]) { if (args.length != 3) { System.out.println("Usage: java MortgageCalcClient < ServerName > < HousePric > < InterestRate >"); System.out.println("Interest rate must be in decimal form!"); System.exit(0); }

String ServerName = args[0]; Integer HousePrice= new Integer(args[1]); Double InterestRate = new Double(args[2]);

try { // create and initialize the ORB 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("JavaWorld MortgageCalc", ""); NameComponent path[] = {nc}; Calculate calc = CalculateHelper.narrow(ncRef.resolve(path));

ResultSet[] v = calc.calcPI(HousePrice.intValue(), InterestRate.doubleValue()); for (int i=0; i < 6; i++) { ResultSet s = v[i]; System.out.println("Term of Loan=" + s.Years + " years"); System.out.println("Down Payment=$" + s.DownPayment); System.out.println("Interest Rate=" + s.InterestRate * 100 + "%"); System.out.println("Monthly Payment=$" + s.MonthlyPayment); System.out.println();

} } catch (Exception e) { System.out.println("ERROR : " + e) ; e.printStackTrace(System.out); } } }

Notice that except for the code used to initialize the ORB and resolve the object reference, all of the other code within this application is virtually identical to that of the RMI client application. To run this application, first start up the Java IDL naming service using the command:

tnameserv

Now, simply start the server and then run the client to see your first Java IDL application in action!

Conclusion

After building these applications using RMI and Java IDL, it’s easy to see that both technologies are fairly similar. It also becomes quickly obvious that each one has several inherent advantages that the other will never be able to match. Java RMI has a huge advantage in the ease-of-use category because the developer assumes from the start that the client and server will both be written in Java. When this assumption is made, the full capabilities of the Java language can be explored. (Recall that in the RMI example, we simply returned a

Vector

object. In the Java IDL example, a more complicated construct was required.) Meanwhile, Java IDL has the advantage of working with legacy code written in C++, COBOL, Ada, or Smalltalk and the added advantage of the CORBA distributed architecture and all its bells and whistles.

Hopefully, this discussion helped clear up some of the issues surrounding JavaSoft’s two-headed distributed objects approach. If you found this discussion interesting and/or enlightening, feel free to let me know. In addition, if you have any topics you would like to see explored in this forum, send them on. See you in June!

Bryan Morgan is a senior member of the technical staff with TASC Inc. (http://www.tasc.com). He currently is using CORBA and Java to build a distributed spatial query application as part of an internal R&D project. Bryan has co-authored several books for Sams Publishing, including Visual J++ Unleashed and Java Developer’s Reference. He holds a B.S. in Electrical Engineering from Clemson University.