Debugging: Root Out Elusive Production Bugs with These Effective Techniques
Smart Tags: Simplify UI Development with Custom Designer Actions in Visual Studio
Ten Essential Tools: Visual Studio Add-Ins Every Developer Should Download Now
XML Comments: Document Your Code in No Time At All with Macros in Visual Studio
Expand Minimize
1 out of 2 rated this helpful - Rate this topic

Migrating Java RMI to Microsoft .NET Interop with the Microsoft Java Language Conversion Assistant 3.0

Visual Studio .NET 2003
 

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:

  1. The "server" creates code that can be accessed through RMI.
  2. 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.
  3. 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.
  4. The client must have a local copy of the proxy class.
  5. 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.
  6. 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.

Did you find this helpful?
(1500 characters remaining)
© 2013 Microsoft. All rights reserved.