ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Local Invocation for CORBA

by Giuseppe Naccarato
10/23/2002

OBV (Object by Value) was introduced in CORBA 2.3 to allow the exchange of CORBA Object value types. This powerful extension is very useful, for example, to design mutable applications. You can combine OBV with RMI-IIOP to allow Java clients to obtain a copy of remote object.

Before the OBV introduction, CORBA did not enable applications to transfer objects from the server to the client by copying. This is an annoying restriction in developing your Java/CORBA applications based on a CORBA vendor implementation, which does not support OBV. In this article, I'm going to explain how you can simulate the pass-by-value strategy in CORBA so that a client virtual machine can locally invoke methods implemented on a remote CORBA server.

Generally in CORBA, a client dial with a reference of a distributed object. Unlike CORBA, RMI -- since its first version -- allows passing of objects both by reference and by copy. In the first case, the behavior is the same as CORBA -- RMI clients obtain a remote reference of an object. In the second case, the whole object (data + code) is transferred to the client application, thus its methods are performed in the same virtual machine as the client.

To figure out why RMI allows the pass-by-value of objects and CORBA didn't, some explanation is necessary. RMI was designed to exclusively work with Java; consequently, it can take advantage of all language features. Using pass-by-value of objects, an RMI server transfers the serialized bytestream of the required Java object to the client. The client application is written in Java as well, so it is able to locally rebuild the object.

Related Reading

Java RMI
By William Grosso

As you know, using CORBA it is even possible to develop client and server applications using different languages. Thus, a CORBA object may be written in C++ and accessed by Java client applications; consequently, the server can't transfer the Java bytestream of this object to the client, simply because there is no Java object. This is the main hindrance why the first versions of CORBA did not enable applications to transfer objects using the pass-by-value strategy.

The final purpose of this article is to simulate pass-by-value in CORBA, obviously in the case that both server and client are developed in Java, without using OBV and RMI-IIOP. I'm going to build a component which transfers the class bytecode from the server to the client application. To achieve this, first you have to figure out how CORBA works and in which way it can transfer an object by reference. I will show this by using an example. Afterwards, using the same example, I will implement an application simulating pass-by-value of objects and, consequently, the local method invocation.

To compile and execute the code presented in this article, you must use J2SE 1.4 with the new Portable Object Adapter (POA).

Remote Invocation

Suppose you would like to implement a remote stack using CORBA. Clients making a connection to such a distributed stack can invoke the pop and push methods in order to insert or take out an element from the stack. This is the IDL for the interface of the distributed stack object:

interface RemoteStack {
  void push(in short value);
  short pop();
};

You can compile the code below using the new idlj compiler included in J2SE 1.4. Therefore, you may implement the object like this:

import java.util.ArrayList;

// RemoteStack implementation
public class RemoteStackImpl extends RemoteStackPOA {

  private ArrayList _s = new ArrayList();

  public synchronized void push(short value) {
    System.out.println("Push: " + value);
    _s.add(new Short(value));
  }

  public synchronized short pop() {
    if (_s.size()==0) return -1;
    short value = ((Short)_s.get(_s.size()-1)).shortValue();
    _s.remove(_s.size()-1);
    System.out.println("Pop: " + value);
    return value;
  }
}

The RemoteStackImpl class uses an ArrayList to store the elements as short. The pop method returns -1 when the stack is empty; otherwise it returns the short value on the top of the stack and removes it from the ArrayList. Using this strategy, you can only use the stack with positive values, but it is enough for our purpose. It is also important to notice that both push and pop show in the console the value of the element. This means you can figure out whether the methods run on the server or on the client side. Actually, the java.util package already provides the Stack class; I used that data structure just as an example for developing a distributed object.

Now you can write a CORBA server that exposes the developed stack implementation.

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

public class RemoteStackServer {

  public static void main(String args[]) throws Exception {

    // Create and initialize the ORB
    ORB orb = ORB.init(args, null);

    // Create the RemoteStack implementation
    RemoteStackImpl impl = new RemoteStackImpl();

    // Activate the POAManager
    POA rootpoa =
      POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
    rootpoa.the_POAManager().activate();

    // Get object reference from the servant
    org.omg.CORBA.Object ref = rootpoa.servant_to_reference(impl);
    RemoteStack href = RemoteStackHelper.narrow(ref);

    // Using NameService
    org.omg.CORBA.Object objRef =
      orb.resolve_initial_references("NameService");
    NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);

    // Bind the "REMOTE_STACK" Object Reference in Naming
    String name = "REMOTE_STACK";
    NameComponent path[] = ncRef.to_name(name);
    ncRef.rebind(path, href);

    // Server Ready
    System.out.println("REMOTE STACK SERVER READY.");
    orb.run();

  }
}

As you may notice, the RemoteStackServer performs the following operation:

Here is a sample of a client application making a connection to the remote stack server.

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

public class RemoteStackClient {

  public static void main(String args[]) throws Exception {

    // Create and initialize the ORB
    ORB orb = ORB.init(args, null);

    // Using NameService
    org.omg.CORBA.Object objRef =
      orb.resolve_initial_references("NameService");
    NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);

    // Resolve the Object Reference in Naming
    String name = "REMOTE_STACK";
    RemoteStack remoteStack = 
      RemoteStackHelper.narrow(ncRef.resolve_str(name));

    // Push (remote invocation);
    remoteStack.push((short)5);

    // Pop (remote invocation);
    short val = remoteStack.pop();
  }
} 

The code attempts a connection to the distributed object named REMOTE_STACK. Then it invokes the push method, passing 5 as a parameter. Eventually, it invokes the pop method and you will see in the server console the following message, proof the distrubuted object works on the server side:

Push: 5
Pop: 5

In fact, the CORBA Name Service returns a reference to the associated implementation of the RemoteStack object. This also means that when more clients invoke the push method on this object, the elements will be inserted on the same distributed object. The push and pop methods work on a single object living on the remote server. In the previous example, you expect the result of pop to be 5, because this is the last value the push method inserted into the stack. This isn't always true. In fact, if, after the push invocation, and before the pop, another client invokes push(18), the shown value will be 18. The point is clear: there is a single stack object, named REMOTE_STACK -- and all CORBA clients get a reference to that object; therefore, the execution of the push and pop methods will be always remote.

Local Invocation

In this section, I will show you how to simulate the local invocation using CORBA. Basically, the idea is the same as behind RMI: transferring the object bytecode from the server to the client application. Still, we make the client able to rebuild the remote object locally starting from the transferred bytecode.

As you know, a compiled Java object consists of a bytecode divided into one or more class files. Indeed, to make the example simpler, I assume there is just one class file. You can implement a CORBA service transferring the class file to the client, which dynamically instances the class by using the Class.forName method. In this way, you can carry out the local execution of Java objects implemented on a remote CORBA Server.

The first step is to implement a CORBA object called ClassLoader, which allows transferring a class file from server to client. The IDL is the following:

interface ClassLoader {
  typedef sequence<octet> bytecode;
  bytecode getImplementation(in string className);
};

The ClassLoader interface defines the bytecode user type consisting of a byte sequence. It declares the getImplementation method as well, which takes a class name as a parameter and returns the associated Java bytecode. The implementation of this method searches for the file called className + ".class" on the server machine, opens this file, and returns the byte sequence of which it consists. You can implement ClassLoader like this:

import java.io.*;
import org.omg.CORBA.*;

// ClassLoader implementation
public class ClassLoaderImpl extends ClassLoaderPOA {

  // Get the java bytecode of the remote object
  public byte[] getImplementation(String className) {
    try {
      // Open the .class file
      RandomAccessFile raf = 
        new RandomAccessFile(className+".class","r");
      // Put the bytes in an array
      byte v[] = new byte[(int)raf.length()];
      raf.readFully(v);
      raf.close();
      // Return the bytes
      return v;
    }
    catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }
}

The class is a CORBA object extending the ClassLoaderPOA-generated skeleton. The getImplementationmethod accesses the class file and fills the v array with the bytes coming from the file. Now you can build the ClassLoaderService CORBA server, exposing ClassLoaderImpl as follows:

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

public class ClassLoaderService {

  public static void main(String args[]) throws Exception {

    // Create and initialize the ORB
    ORB orb = ORB.init(args, null);

    // Create the ClassLoader implementation    
    ClassLoaderImpl impl = new ClassLoaderImpl();

    // Activate the POAManager
    POA rootpoa = 
      POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
    rootpoa.the_POAManager().activate();

    // Get object reference from the servant
    org.omg.CORBA.Object ref = rootpoa.servant_to_reference(impl);
    ClassLoader href = ClassLoaderHelper.narrow(ref);

    // Using NameService
    org.omg.CORBA.Object objRef =
      orb.resolve_initial_references("NameService");
    NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);

    // Bind the "CLASSLOADER" Object Reference in Naming
    String name = "CLASSLOADER";
    NameComponent path[] = ncRef.to_name(name);
    ncRef.rebind(path, href);

    // Server Ready
    System.out.println("CLASSLOADER SERVICE READY.");
    orb.run();

  }
}

Clients accessing ClassLoaderService obtain the CORBA object named CLASSLOADER, which is a ClassLoaderImpl instance. Using the getImplementation method, a client will be able to load any class file stored on the server machine. Then, the client can use the bytecode to locally rebuild the Java object. Finally, we need a further service that converts the bytecode in a class file, writes that file on the client machine and, using the Class.forName method, instances the object dynamically. You can make this client facility in this way:

import java.io.*;

public class ClassMaker {

  // Create a class instace starting form the bytecode
  public static Object make(String className, byte[] bytecode) {
    try {
      // Write the .class file
      RandomAccessFile raf = 
        new RandomAccessFile(className+".class","rw");
      raf.write(bytecode);
      // Instace and return the object
      return Class.forName(className).newInstance();
    }
    catch (Exception e) {
      e.printStackTrace();
      return null;
    }
  }
}

The ClassMaker class contains the static method make, having as input the class name and the associated bytecode. By using Class.forName and newInstance, it returns an instance of the class behind the bytecode. Figure 1 shows the process to obtain a local copy of an object implemented by a CORBA server.


Figure 1. Obtaining a local copy of a CORBA server-implemented object.

As you may notice, the client application requires the CLASSLOADER service to obtain a local instance of MyObjectobject. The ClassLoader implementation, invoking getImplementation, searches for the MyObjectImpl.class file and converts it into a sequence of bytes. That sequence will be returned to the client application, which builds the MyObject.class file on the client machine by using ClassMaker. Eventually, the client invokes Class.forName and newInstance to create a local concrete instance of MyObject. It is very important that the MyObject interface be present in both the client and server applications. For the client, this is necessary because the make method of ClassLoader returns a generic Java object. Therefore, the client has to cast such a generic object with the right interface that is MyObject. Obviously, the interface must be present on the server side as well, because it is implemented right there.

As stated earlier, both the client application and the MyObject implementation must be developed in Java. On the contrary, ClassLoadService and ClassLoader can be written in other languages supporting IDL and CORBA. In fact, their duty is not Java-dependent -- they just allow transferring a file from the server to the client. Futher proof of this concept: I have written an IDL interface for ClassLoader but not for MyObject.

Local Stack

In this section, I will develop an example representing a client application that obtains a copy of a remote stack object by using the ClassLoaderSevice service and ClassMaker. The first step is writing the Java interface for the Stack:

public interface Stack {
  public void push(short value);
  public short pop();
}

Then, you can implement that interface on the CORBA server in this manner:

import java.util.ArrayList;

public class StackImpl implements Stack {

  private ArrayList _s = new ArrayList();

  public synchronized void push(short value) {
    System.out.println("Push: " + value);
    _s.add(new Short(value));
  }

  public synchronized short pop() {
    if (_s.size()==0) return -1;
    short value = ((Short)_s.get(_s.size()-1)).shortValue();
    _s.remove(_s.size()-1);
    System.out.println("Pop: " + value);
    return value;
  }
}

After compiling StackImpl, you can find on the server machine the StackImpl.class file. A client application that wants to obtain a local instance of StackImpl has to make a connection to ClassLoaderService service for getting a ClassLoader implementation. Then, invoking getImplementation, it receives the bytecode of the StackImpl object (which is stored in StackImpl.class). Finally, the client application can build a local instance of StackImpl by using the ClassMaker class.

You can delegate the details about the creation of the Stack instances to a factory, as shown in this code:

public class StackFactoryClient {

  private ClassLoader _loader = null;

  // Set the loader instance
  public StackFactoryClient(ClassLoader loader) {
    _loader = loader;
  }

  // Return a local instance of StackImpl
  public Stack getLocalStack() {
    // Use the ClassLoader service
    byte bytecode[] = _loader.getImplementation("StackImpl");
    // Make the object
    return (Stack) ClassMaker.make("StackImpl",bytecode);
  }
}

The constructor of the StackFactoryClient class takes as the input parameter a ClassLoader object, which represents a reference to the active implementation of ClassLoaderImpl, which is the distributed object named CLASSLOADER. That reference will be assigned to the _loader attribute. The factory contains the getLocalStack method, calling the _loader.getImplementation("StackImpl") method and returning the instance of StackImpl built by ClassMaker. By means of StackFactoryClient, each client application can get a local instance of a distributed Stack object.

At this point, you are ready to implement a simple client application obtaining a local stack implementation by using the ClassLoader service. The code is this:

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

public class StackClient {

  public static void main(String args[]) throws Exception {

    // Create and initialize the ORB
    ORB orb = ORB.init(args, null);

    // Using NameService
    org.omg.CORBA.Object objRef = 
      orb.resolve_initial_references("NameService");
    NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);

    // Resolve the Object Reference in Naming
    String name = "CLASSLOADER";
    ClassLoader loader =
      ClassLoaderHelper.narrow(ncRef.resolve_str(name));

    // Stack Factory
    StackFactoryClient factory = new StackFactoryClient(loader);

    // Create a local Stack Reference
    Stack stack = factory.getLocalStack();

    // Push (local invocation);
    stack.push((short)5);

    // Pop (local invocation);
    short val = stack.pop();

  }
}

The loader variable represents the Java reference of the CLASSLOADER distributed object. The program creates an instance of StackFactoryClient by passing loader as a parameter of the constructor. Then, getLocalStack returns the local instance of Stack, which is stored in the stack variable. Here, push and pop work on the client virtual machine, hence the output:

Push: 5
Pop: 5

will be shown in the client console.

Limitations

The explained strategy presents some limitations. First, client and server applications must be written in Java. Second, the bytecode of a class is not always held in one class file. For the StackImpl class, it was just one class file, but in other complex situations this is not always true, especially when there are inner classes. If you need ClassLoaderImpl to work properly even in those cases, you must change the implementation in order to support multi-file transfer. A very efficient solution might be to archive the class files into a single jar and transfer it. Using this approach, you have to provide that the CLASSPATH environment variable references this jar file.

This proposed solution doesn't deal with packages. In the previous examples, I didn't use package names for the classes. When a class belongs to a package, it is placed in a directory that has the same name as the package. Therefore, the ClassLoaderService should be modified in order to search for the class file on the right server directory and transfer it to the same client directory. To fix this, you can use a jar file, as well. In this manner, the transferred file will be a single jar and, as you know, the name of the package is specified just inside of it.

Conclusion

Obtaining a local instance of distributed CORBA objects may be useful in some circumstances. Suppose you want to provide a CORBA service to run a heavy algorithm requiring many CPU hours. If this component is a Web service, a thousand applications could make a connection with it. In this case, the host machine could be so busy that it couldn't satisfy all requirements efficiently. To avoid that, you can let the service return not the result of the elaboration but the code of the algorithm, which will be executed on the client machine. You can do this using the pass-by-value strategy as I have mentioned. Hence, such a CORBA server will be a class provider and not, as usually is the case, an object provider.

You should note, however, that the latest CORBA 2.3 now supports the object-by-value; therefore, you can carry it out in java by RMI-IIOP.

Bibliography

Q. Mahmoud, "CORBA Programming with J2SE 1.4." java.sun.com, May 2002.

Giuseppe Naccarato has a degree in computer science and works as software developer for an IT company based in Glasgow (UK). His main interests are J2EE- and .Net-related technologies. Contact Giuseppe at http://www.giuseppe-naccarato.com.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.