Migrating Java RMI to Microsoft .NET Interop with the Microsoft Java Language Conversion Assistant 3.0
Microsoft Corporation
September 2005
Applies to:
Microsoft Java Language Conversion Assistant 3.0
Microsoft .NET Framework
Summary: This article describes how to use the Microsoft Java Language Conversion Assistant to convert Java Remote Method Invocation (RMI) to Microsoft .NET Remoting. (9 printed pages)
Note More information on the Java Language Conversion Assistant (JLCA) can be found on the Microsoft Visual Studio Developer Center here.
Contents
The Business Problem
The Java Technology
The Conversion Process
Leveraging .NET
The Business Problem
The nature of an application often changes when you are working in an enterprise environment. Quite often, an enterprise application has to bridge between two or more computers. One computer (the client) needs to access code running on another computer (the server). In Java, this form of distributed computing primarily takes place using Remote Method Invocation (RMI). If you are moving from Java to the Microsoft .NET Framework, you need a mechanism that replicates the RMI technology in order to bridge systems.
The Java Technology
RMI is based on a wire technology very similar to the Common Object Reference Broker Architecture (CORBA). This architecture creates a multi-step process for achieving cross-computer connectivity:
- The "server" creates code that can be accessed through RMI.
- A proxy class of the server code must be created. The proxy class provides an interface where the client can see the exposed methods but cannot see the wire protocol embedded in the class.
- An instance of the server code has to be stored in an RMI registry of some sort. RMI registries are often built into J2EE application servers or LDAP systems.
- The client must have a local copy of the proxy class.
- The client connects to the RMI registry (usually through JNDI) to access the instance of the server code, and binds the server code to the proxy class.
- The client makes calls through the proxy class to the server code.
Although this multi-step process solves the problem of hiding the wire protocol, the whole process can be rather cumbersome. Specifically, any changes made to the server code require that you not only regenerate the proxy class, but also rebind the instance to the server code and make sure that the client has the correct proxy.
The Server Code
In an RMI scenario, the server code usually consists of at least four pieces: an interface defining the class, an implementation, the proxy, and a class that registers an instance of the implementation with the RMI registry.
The Interface
The interface class must extend the java.rmi.Remote interface. The Remote interface is a marker that tells the Java compiler that the class will be remotely accessible.
import java.rmi.*;
public interface InterestCalculator extends Remote {
public void setTerms(double principal, double rate, int months) throws RemoteException;
public double getTotalCost() throws RemoteException;
public Payment[] getPayments() throws RemoteException;
}
Listing 1. The Remote Interface
You should also note that every remotely accessible method must declare that it throws the RemoteException. The RemoteException is a catchall for any problems that occur when a client is trying to access the server object.
The Implementation
A typical implementation class must not only implement the interface, but it must also extend java.rmi.server.UnicastRemoteObject. This object contains all of the serialization and wire transfer code necessary to establish communication between virtual machines. The implementation class must also throw RemoteException on every method, including the constructor.
import java.rmi.*;
import java.rmi.server.*;
public class CalcImpl extends UnicastRemoteObject implements InterestCalculator {
private double principal;
private double rate;
private int months;
public CalcImpl() throws RemoteException {
/**no initialization required**/
}
public void setTerms (double principal, double rate, int months)
throws RemoteException {
this.principal = principal;
this.rate = rate;
this.months = months;
}
public double getTotalCost() throws RemoteException {
return months*(principal/getAmortization());
}
private double getAmortization() {
double numerator= Math.pow((1 + rate/12), months)-1;
double denominator= (rate/12) * Math.pow((1 + rate/12), months);
return numerator/denominator;
}
public Payment[] getPayments() throws RemoteException {
Payment[] payments = new Payment[months];
double interest; //interest payment
double principalLeft = principal;
double pay = p rincipal/getAmortization();
double thispay;
for (int i=0;i<months;i++) {
interest = principalLeft*rate/12;
principalLeft += interest;
if (pay>principalLeft) {
thispay = principalLeft;
}
else {
thispay = pay;
}
payments[i] = new Payment(i+1, (thispay-interest), (interest));
principalLeft -= thispay;
}
return payments;
}
}
Listing 2. The Implementation Code
The Payment Bean
We also have a Payment bean that encapsulates the principal, interest, and due date information for each payment on the loan. The bean code is very basic, and is only notable because it has to implement Serializable and be part of the code package that is available to the client.
The Proxy
Once we've built our implementation code, we have to generate the proxy class using the rmic command line tool that comes with the Java SDK. This tool creates a mirror of our implementation class, where every call to a method actually serializes the arguments and sends the call over the wire to the actual server code instance. In other words, the client code thinks it's talking to CalcImpl, but it's actually talking to a proxy class that hides all of the wire transfer code necessary to call the real CalcImpl class.
Registration
The last part of the server side of an RMI system consists of creating an instance of CalcImpl and registering it with an RMI registry. The simplest way to illustrate this process is to use the RMIRegistry service built into the Java SDK and avoid the JNDI naming calls:
import java.rmi.*;
public class RegisterCalc {
public static void main(String[] args) throws Exception {
CalcImpl ci = new CalcImpl ();
Naming.rebind("CompoundInterest", ci);
} //main
} //class
Listing 3. Registering the Class
As long as the RMIRegistry service is running on the local server, this code will create a calculator instance and register it under the name CompoundInterest. We'll have to make sure that the RMIRegistry service is running, and that we've run our RegisterCalc class before we can access the code from a client.
The Client Code
Before we can do anything with our client, we have to make sure that we have a local copy of the proxy class and any other classes that are used by the Calculator. In this case, we need the proxy, the InterestCalculator interface, and a copy of the Payment object. Once we have these classes, we can make a call to the RMI registry to bind to the CompoundInterest object, and then we can make calls as if we had a local instance of CalcImpl.
import java.rmi.*;
public class CalcClient {
public static void main(String[] args) {
String rmiURL = "rmi://localhost/CompoundInterest";
double payBack = 0;
java.text.DecimalFormat df = new java.text.DecimalFormat("#.00");
try {
//Get an instance of the class
InterestCalculator ic =
(InterestCalculator)Naming.lookup(rmiURL);
//execute the method
ic.setTerms(100000f, 0.085f, 60);
payBack = ic.getTotalCost();
System.out.println("The total cost on a 5 year,"
+" 8.5% loan of $100,000 is: $" + df.format(payBack));
Payment[] payments = ic.getPayments();
for (int i=0; i < payments.length; i++) {
Payment p = payments[i];
System.out.println("Payment #: " + (i+1) +
" Total: " + df.format(p.getPayment()) +
" Interest: " + df.format(p.getInterest()));
}
} catch (Exception e) {
System.out.println(e);
e.printStackTrace();
} // try catch
} //main
} //class
Listing 4. The Client
The Conversion Process
Before looking at the conversion process, we have to start by looking at .NET Remoting and how it differs from RMI. Although both systems solve the same business problem (connecting distributed systems), the approaches are significantly different. These differences have a big impact on the conversion process.
First, .NET Remoting isn't tied to any particular serialization mechanism or transmission protocol. RMI is restricted to the CORBA standards for communication, which define both the serialization and the transport options. A .NET Remoting application could, for example, transmit its data using encrypted binary data over TCP/IP, or raw XML over HTTP. This flexibility is possible through the use of formatters, which translate and package the messages, and channels which provide the transport mechanism. When you convert RMI to .NET Remoting, you will have to adjust the various .NET Remoting options to select an appropriate channel and formatter.
Second, where RMI requires that you connect to an RMI registry to find your objects, or use JNDI to lookup the object from the registry, .NET Remoting allows several other options, including direct connectivity. In other words, you can set up your channels and formatters in a configuration file, and let your application connect directly to the target class, without having to go through any registry. If you want to use a registry, you can host .NET Remoting objects in many different places, including a Microsoft IIS Web directory.
When we look at the conversion, you will see how the Microsoft Java Language Conversion Assistant (JLCA: see http://msdn.microsoft.com/jlca) tries to mimic the RMI behavior as much as possible. However, in practice, you will find that you can replace a lot of the converted RMI code with simplified .NET Remoting pieces that skip many of the steps imposed by the RMI architecture.
The Server Code
On the server side, the primary changes to the code reflect the different philosophy between the .NET Framework and the Java framework.
The Interface
Our interface remains essentially unchanged, except that the get/set methods have been replaced with properties. The only other change is that the interface is put into its own namespace to help facilitate the .NET Remoting portion of the code.
using System;
namespace IRemoting
{
public interface InterestCalculator
{
double TotalCost
{
get;
}
Payment[] Payments
{
get;
}
void setTerms(double principal, double rate, int months);
}
}
Listing 5. The Interface
The Implementation
Our implementation of the interface contains very few changes from the Java version. The most notable is that the base class was changed from UnicastRemoteObject to System.MarshalByRefObject. The .NET Framework class provides essentially the same functions as UnicastRemoteObject.
using System;
[Serializable]
public class CalcImpl:System.MarshalByRefObject, IRemoting.InterestCalculator
{
//code omitted for brevity
}
Listing 6. The Implementation Class
Once again, the various get/set methods have been reworked into .NET Framework-style properties to match the interface. For example, the getTotalCost() method has been converted as follows.
virtual public double TotalCost
{
get
{
return months * (principal / Amortization);
}
}
Listing 7. Java get/set Method Converted to a Property
You may note that the methods are declared as virtual. This keyword indicates that you can create a derived class and override this method. If you omit the virtual keyword in Microsoft C#, the method is effectively equivalent to a Java final method.
The Payment Bean
The Java Payment class was converted without error, although the get/set methods were replaced with properties. The only other significant change in this bean involves replacing the Serializable marker interface with the equivalent C# attribute. In the case of our Payment class, this involves adding a single declarative tag before the class definition.
[Serializable] public class Payment
Listing 8. The Serializable Attribute
This change reflects a design difference between the .NET Framework and Java. In the .NET Framework, marker interfaces and other declarative information have been moved to external attributes.
The Remoting Process
Unlike with Java RMI, the .NET Remoting framework does not require that you run a special process to create a proxy class. The Common Language Runtime takes care of this step automatically when you create an instance of your calculator implementation. Therefore, all you have to do is set up a registration class that defines the channel, transport, port, and binding name. The code can be reduced to a few simple lines.
using System;
using System.Runtime.Remoting;
public class RegisterCalc
{
[STAThread]
public static void Main(System.String[] args)
{
Channels.ChannelServices.RegisterChannel(new Channels.Http.HttpChannel("8080"));
CalcImpl ci = new CalcImpl();
RemotingServices.Marshal(ci, "CompoundInterest");
System.Console.ReadLine();
}
}
Listing 9. Registering the Server Class
In this example, we are registering our calculator to the localhost on port 8080, with a name of CompoundInterest. If we wanted to register the calculator to another host, we would just modify the second argument of the Marshal command to indicate the full URI for the remote object.
Also, we have to keep our server class alive so that the class will keep running until someone stops the application by pressing ENTER in the console window. In a production environment, we would want to make this class a Microsoft Windows service to ensure that our calculator was always instantiated and registered.
The Client Code
Surprisingly, the client code contains most of the errors related to the conversion. Of the six errors, only one relates to RMI. The other five errors have to do with differences between the Java and .NET Framework implementations of exceptions and console access, or the fact that the JLCA couldn't convert the DecimalFormat class because C# uses something entirely different.
However, the only interesting part, for our purposes, is that we only need one line of code to connect to our remote object.
IRemoting.InterestCalculator ic = (IRemoting.InterestCalculator)
Activator.GetObject(typeof(IRemoting.InterestCalculator),
"http:8080\\localhost\CompoundInterest);
Listing 10. Connecting to the Remote Code
The Activator.GetObject command is very similar in function to the Naming.Lookup() command from Java. We have to pass in an object type (the Interest Calculator interface) and a URI that points to our registered instance.
Once we have our object, the rest of the client code works just like the Java version (with various syntax changes for C#).
Leveraging .NET
One of the major advantages of .NET Remoting is that you can choose your own transport and channel mechanisms. For example, if you wanted to run your remoting calls over TCP instead of HTTP, you would simply replace the first line in Listing 8 with the following line.
Channels.ChannelServices.RegisterChannel(new Channels.Tcp.TcpChannel("1234"));
Listing 11. Switching Channels
You don't have to change the client code at all (except to point to the new URI). Not only can you use the built-in channels, but you also can write your own, to take advantage of any transport protocol.
You can further extend remoting by adding formatters inline with your channel. A formatter changes the way your arguments are serialized and de-serialized. By default, the HTTP channel uses SOAP formatting for all messages. However, you can configure the channel to use binary encoding, or you can simply write your own formatter to use any serialization format you want. You might, for example, create an encrypted formatter that encodes all of your remote calls using a secure encryption algorithm.
As long as both the client and server have access to the same formatter, you can literally use any transmission format you want. In fact, several companies have built, or are working on, RMI to Remoting bridges based on this exact model. On the Java side, your code uses basic RMI calls. On the .NET Framework side, your code would use a custom channel and custom formatter in order to communicate with the RMI objects.