MokaByte Numero 14 - Dicembre 1997

Java Reflective Broker 

 

di  

Ennio Grasso
 Specification
 

 
 

Introduction

Java Reflective Broker (JRB - pronounced jerby) is a distributed object model for the Java language that retains the semantics of the Java object model, making distributed objects easy to implement and to use. The system uses two important frameworks of the JDK1.1, namely Java Serialization and Java Reflection.
 

Rationale
Distributed systems require that computations running in different address spaces, potentially on different hosts, be able to communicate. For a basic communication mechanism, the Java language supports sockets, which require the client and server to engage in applications-level protocols to encode and decode messages for exchange, and the design of such protocols is cumbersome and error-prone.

An alternative to sockets are Object Request Brokers (ORBs) that abstract away the communication interface to the level of a remote method invocation. Instead of working directly with sockets, the programmer has the illusion of calling a local method, when in fact the arguments of the call are packaged up and shipped off to the remote target of the call. Traditional ORBs, such as CORBA and DCOM, are designed to work across languages, operating systems, and hardware platforms, thereby falling short of seamless integration with the specific features of a certain environment. CORBA users know that mapping IDL constructs into programming languages, such as C, C++, Smalltalk or Java, can be clumsy and unnatural.

The new Java Development Kit 1.1 supports Java RMI (Remote Method Invocation) which assumes the homogeneous environment of the Java Virtual Machine and can therefore take advantage of the Java object model whenever possible. Though Java RMI is far simpler than CORBA, some drawbacks still remain:

On account of the above we have developed JRB. JRB is not a competitor of Java RMI but a niche product targeted for those applications that care for the aforementioned issues, in particular JRB:  

JRB Architecture
The architecture of the JRB essentially builds upon two primary classes, the ObjectRef class and the Server class that represents respectively the client and server side of the JRB architecture.

ObjectRef class

When writing an applet or an application that uses remote objects, the programmer needs to be aware of the JRB client visible interfaces. JRB client functions are provided by the cselt.jrb.ObjectRef class used to:

package cselt.jrb;

import java.io.*;

import java.lang.reflect.*;



public class ObjectRef implements Serializable {



        public static String getLoaderName() {...}

        public static void setLoaderName(String name) {...}



        public ObjectRef(URL url) {...}

        public ObjectRef(String url) throws MalformedURLException {...}



        public String getHost() {...}

        public int getPort() {...}

        public String getObjectName() {...}



        public String toString() {...}



        public Object invoke(String methodName, Object args[])

              throws Exception {...}

}

Server class
JRB server functions are provided by the cselt.jrb.Server class. This class provides support for point-to-point active object references using Java Serialization as on the wire protocol. The Server class implements a remote server object with the following characteristics: The Server class provides only static methods and is used to:

package cselt.jrb;

import java.io.*;

import java.lang.reflect.*;



public class Server {



        public static void setPort(int port) throws Exception {...}

        public static int getPort() {...}



        public static ObjectRef export(Object obj) {...}

        public static ObjectRef export(String objectName, Object obj) {...}



        public static void retract(String objectName) {...}

        public static void retract(ObjectRef ref) {...}



        public static Object lookup(String objectName) {...}

}

The NetClassLoader class
The cselt.jrb.NetClassLoader is a utility class that can be used by applications to load classes via a URL.

package cselt.jrb;

import java.io.*;

import java.net.*;



public class NetClassLoader extends ClassLoader {



        public static void setURL(URL url) {...}

        public static void setURL(String url)

                     throws MalformedURLException {...}

        public static URL getURL() {...}



        public Class loadClass(String name)

                     throws ClassNotFoundException;

}

The loadClass method loads the specified class name via the URL defined by the setURL method. The class is loaded, defined, and returned. The JRB runtime may create instances of this class to load classes of arguments received in remote invocations if such classes cannot be found locally by the default class loader. Server processes must declare the location of the classes (arguments/returns) by setting the URL, using the normal protocols, such as http and ftp, with the setURL method of the NetClassLoader class, e.g.

NetClassLoader.setURL("http://loom.cselt.it/~ennio/java/codebase/");

Although the JRB system provides the NetClassLoader class, the programmer is given the chance to customize the behaviour of class loading, for example by providing a class loader that loads class with an alternative mechanism to the URL-based model. All the programmer has to do is define a class loader, e.g. by inheriting from cselt.jrb.NetClassLoader or from java.lang.ClassLoader, and then let the JRB runtime know of this new class. For example, suppose your class loader is mypackage.MyClassLoader, then you simply call:

ObjectRef.setLoaderName("mypackage.MyClassLoader");

before any remote invocation is performed.
 

Naming class
For a client to be able to invoke a method on a remote object, that client must first obtain an object reference. Object references are created by exporting an object with the JRB runtime system through the export operation of the Server class. Most of the time the client will obtain a reference to a remote object as a parameter to, or a return value from, another remote method call. For bootstrapping, the client must be able to obtain the first reference to a remote object. The simplest way is for the server to listen on a well known port and export an object with a name also known by the client. The client can use a URL-based naming scheme of the form host:port/objectName to create an object reference, where objectName is a simple string name used in the export operation:

ObjectRef ref = new ObjectRef("loom.cselt.it:9999/MyObjectName");

If starting a server on a well known port is considered restraining, the JRB system provides a Naming Server. The cselt.jrb.Naming class provides methods for lookup, binding, rebinding, unbinding, and listing the contents of the Naming Server using a URL-based naming model. Being Naming a remote object, clients use the JRB to invoke the methods of the Naming class. This can be done either through the invoke method of the generic object reference ObjectRef, e.g.

ObjectRef.invoke("bind", "MyObjectName", objRef);

or through the method of the specialized object reference NamingRef generated by the stub generator, e.g.

NamingRef.bind("MyObjectName", objRef);

package cselt.jrb;

import java.io.*;

import java.net.*;



public class Naming {



public static int DEFAULT_PORT = 6666;



public ObjectRef lookup(String name)

         throws IOException {...}



public void bind(String name, ObjectRef objRef)

         throws IOException {...}



public void rebind(String name, ObjectRef objRef) {...}



public void unbind(String name)

         throws IOException {...}



public String[] list() {...}

}

Stub generator
Normally, the JRB will be used without stubs. The invoke operation of the ObjectRef class follows the principle of the Java Reflection API that dynamically creates an invocation and dispatches the call to the remote object. If, for any reasons, the programmer prefers to use the static invocation model, like the one supported by Java RMI, JRB provides a stub generator for creating specialized object references. A specialized object reference class is a subclass of ObjectRef that provides the same public method of the exported remote object. For example, the Naming Server of the JRB is made up of the Naming class. By applying the stub generator to this class a NamingRef class is created that provides the same methods of the Naming class and is used at the client side for remote invocations to the Naming object.

The compiler itself is written in Java and must be launched with:

java cselt.jrb.Stub <flags> <className>

<className> is the package qualified class name of the remote object class. The class must have previously been compiled successfully with javac. <flags> are all the flags applicable to the javac compiler plus the optional flag -package <packageName>. If this flag is given, the package name of the specialized object reference class will be <packageName>, otherwise the package will correspond to the remote object class’s package. This option is useful if you need to remote-enable a library class. For instance to remote-enable the Vector class of the java.util package you should not create a specialized object reference in java.util, rather, you may call:

java cselt.jrb.Stub -package examples.hello java.util.Vector

this creates a VectorRef class in package examples.hello.
 

JRB through Firewalls
Many intranets have firewalls that do not allow opening direct sockets to hosts on the Internet. Therefore JRB uses http as the default transport layer to enable a client behind a firewall to invoke a method on a remote object which resides outside the firewall. The JRB transport layer embeds serialized objects in the body of an http POST request and response. The JRB runtime on the target machine listens with a server socket that is capable of understanding and decoding JRB invocations inside http POST requests.

If the target object resides on a machine that is not visible by the client, JRB provides a proxy servlet to forward an invocation to the target JRB object. Servlets are the dynamic Java extensions to web servers as applets are to web browsers. Suppose that JRB object, say ObjectServer, listens on machine loom.cselt.it on port 9999 which is inside an intranet and is not visible by a remote client in the Internet. However, that intranet is connected with the Internet through a web server on machine andromeda.cselt.it. If the web server supports Java servlets, the client can indeed invoke ObjectServer by construing an object reference of the kind:

ObjectRef ref = new ObjectRef("http://andromeda.cselt.it/servlet/

cselt.jrb.ProxyServlet?http://loom.cselt.it:9999/ObjectServer");
This URL is delivered to the web server listening on andromeda.cselt.it causing the dynamic loading and invocation of JRB's ProxyServlet servlet. This servlet uses the query argument to determine the target object and deliver the invocation to ObjectServer on loom.cselt.it. Objviously this approach only works if the web server supports the servlet extension. A similar approach, though more time consuming, can be realized using CGI scripts.
 

JRB Event Service
The standard JRB architecture supports the synchronous, point-to-point, connection-oriented communication model. In this model the client is explicitly aware of which target object it is invoking. The JRB Event Service supports an asynchronous, multicast, connection-less communication model where a sender object, the talker, sends a message and is unaware of the identity and number of recipients, the listeners, of the message. Consequently any messages is uni-directional, from one or more talkers to one or more listeners. In general, M talkers can issue messages via the same message stream to N listeners, without any of the talkers and listeners having explicit knowledge of each other. An advantage of this approach is that new talkers and listeners can be added easily.

Topic names
In Java Beans event model, listener objects interested in certain events must know the identities of the source objects of those events and register directly with them, which, in turn, have to maintain a list of the interested listeners to forward the events. The JRB Event Service follows the standard Java Beans event model but now listeners do not have to know the identity of the talkers and talkers do not have to maintain a list of listeners to forward the events. Events are identified by topic names, and listeners specify events of interest by informing the Event Service of topics interest to them. Topic names are organized into a hierarchical tree structure, like a file system. When a talker sends an event, the topic name of the event identifies a branch of a tree, e.g. "/cselt/teleconf/10:00am". All listeners that have registered for topic names along this branch will be notified of the event. In the example listeners that have registered for: "", "/cselt", "/cselt/teleconf", "/cselt/teleconf/10:00am" are all notified. The further down the tree hierarchy, the more specific the event registration becomes.

Talker class
The Talker class provides support for sending either singlecast or multicast events depending on the IP address specified in the constructor. If the address denotes an IP mulitcast group (Class D Internet address in range 224.0.0.0 to 239.255.255.255), all listeners that have joined that multicast group will receive the event, possibly on several different Virtual Machines. If, on the other hand, the address identifies a certain host-name/port-number, the talker will send the event only to the listeners in the Virtual Machine identified by that address. The cselt.jrb.Talker class is used to:

package cselt.jrb;

import java.io.*;



public class Talker implements Serializable {



        public Talker() {...}

        public Talker(String group) {...}

        public Talker(String group, int port) {...}



        public String getAddress() {...}

        public int getPort() {...}



        public String toString() {...}



        public void send(String topicName, Object arg)

              throws Exception {...}

}

Due to browsers' security policies, applets are not usually allowed to use IP multicast addresses. However, an applet is allowed to listen to events sent by the host whence the applet was downloaded.

Listener interface
Listeners must implement the cselt.jrb.Listener interface. This interface provides a single operation, receive, which is invoked to notify the listener of the occurrence of the event.

package cselt.jrb;

import java.io.*;



public interface Listener extend java.util.EventListener {

        public void receive(String topicName, Object arg);

}

ProxyListener class
The cselt.jrb.ProxyListener class provides only static methods and is used to:

package cselt.jrb;

import java.io.*;



public class ProxyListener {



        public static int packetSize = 4096;

        public static boolean multiThread = true;



        public static void setMulticastAddress() {...}

        public static void setMulticastAddress(String address)

                                    throws Exception {...}

        public String getAddress();

        public static void setPort(int port) throws Exception {...}

        public static int getPort() {...}



        public static void addListener(String topicName, Listener l) {...}

        public static void removeListener(String topicName, Listener l) {...}

}

JRB and RMI comparison
In the following we provide a brief comparison between Java RMI and JRB.

Remote-enabling a class
With RMI, the following steps are required to enable an existing class for remote method invocation:

Note that instances of classes defined as Remote cannot be passed by value. Therefore, if you define a RemoteVector class and pass it as an argument, it will always pass a remote reference to the vector rather than a copy of the vector. This is not always the desired effect.

With JRB, to enable an existing class for remote method invocation, simply export an instance of the class with the export method of the Server class. You do not have to modify the original class in any way, not even access to its source code. This means that you can effortlessly enable any third-party library for remote method invocation. For example, to remote enable an instance of the java.util.Vector class simply export it:

...

Vector v = new Vector();

Server.export(v);

Exporting a Named Remote Object
Both RMI and JRB provides a Naming Server, called registry in RMI, for binding an object to a name, then retrieving the object via its name. To create an object in a server and then export the object for use by remote clients, the following steps are required:

In addition, JRB includes an integrated URL-based naming scheme that allows you to create an object reference from a URL name without the need for a separate Naming Server. For example, you can export an instance of java.util.Vector with name "MyVector" on a server listening on port 9999 on host loom.cselt.it:

Server.setPort(9999);

Server.export("MyVector", new Vector());

then clients can bind the remote object by creating an object reference:

VectorRef vector = new VectorRef("loom.cselt.it:9999/MyVector");

Garbage Collection
RMI has a lease-based garbage collection system. When a client obtains a reference to a remote object, the client is granted a lease that must be periodically renewed in order to prevent the remote object from being garbage collected. A remote object is garbage collected when all of its leases have expired. This garbage collection mechanism may severely affect the performance of the application due to the considerable overhead on the wire. Instead of relying on a distributed garbage collection mechanism, in JRB servers maintain control on the exported objects. Objects are not garbage collected till they are explicitly retracted.

Product Size
RMI's total class file size is 175KBs uncompressed. This includes all primary RMI .class files, the registry, and the distributed garbage collection system but excludes the http firewall support. JRB's total class file size is less than 20KBs uncompressed, an order of magnitude less than RMI!

Stubs and Skeletons
RMI uses the rmic utility to generate stubs and skeletons for remote method invocation. If using the dynamic invocation model, JRB doesn’t need stubs and skeleton. If the programmer prefers to use a static invocation model JRB provides a stub generator. This section shows the code that each system generates for the twoInts method of the following interface:

public interface MyInterface extends Remote {
public int twoInts(int a, int b) throws RemoteException;
}

As the code examples below demonstrate, JRB typically emits 90% less support code than RMI. The stub code for twoInts with RMI:

public int twoInts(int $_int_1, int $_int_2) throws java.rmi.RemoteException {

        int opnum = 1;

        java.rmi.server.RemoteRef sub = ref;

        java.rmi.server.RemoteCall call =

                sub.newCall((java.rmi.server.RemoteObject)this, operations,

                        opnum, interfaceHash);

        try {

                java.io.ObjectOutput out = call.getOutputStream();

                out.writeInt($_int_1);

                out.writeInt($_int_2);

        } catch (java.io.IOException ex) {

                throw new java.rmi.MarshalException("Error marshaling arguments", ex); };

                try {

                        sub.invoke(call);

                } catch (java.rmi.RemoteException ex) {

                        throw ex;

                } catch (java.lang.Exception ex) {

                        throw new java.rmi.UnexpectedException("Unexpected exception", ex);

        };

        int $result;

        try {

                java.io.ObjectInput in = call.getInputStream();

                $result = in.readInt();

        } catch (java.io.IOException ex) {

                throw new java.rmi.UnmarshalException("Error unmarshaling return", ex);

        } catch (Exception ex) {

                throw new java.rmi.UnexpectedException("Unexpected exception", ex);

        } finally {

                sub.done(call);

        }

        return $result;

}

The skeleton code for twoInts with RMI:

case 0: { // twoInts

        int $_int_1;

        int $_int_2;

        try {

                java.io.ObjectInput in = call.getInputStream();

                $_int_1 = in.readInt();

                $_int_2 = in.readInt();

        } catch (java.io.IOException ex) {

                throw new java.rmi.UnmarshalException("Error unmarshaling arguments", ex);

        } finally {

                call.releaseInputStream();

        };

        int $result = server.twoInts($_int_1, $_int_2);

        try {

                java.io.ObjectOutput out = call.getResultStream(true);

                out.writeInt($result);

        } catch (java.io.IOException ex) {

                throw new java.rmi.MarshalException("Error marshaling return", ex);\

        };

        break;

}

The stub code for twoInts with JRB:

public int twoInts(int p1, int p2) throws Exception {

        Object o[] = {new Integer(p1), new Integer(p2)};

        return ((Integer)invoke("twoInts", o)).intValue();

}

There is no skeleton code because JRB does not require skeleton code on the server side.
 
 
 
Ennio Grasso appartiene alla linea di Tecnologie Softeware di CSELT. Occupandosi di object-oriented distributed computing da quando è entrato in CSELT nel 1992, ha maturato una certa esperienza sia progettuale che implementativa. 
È rappresentante CSELT in OMG e segue lo standard CORBA sin dalle prime fasi. Autore del modello transazionale 
dell'architettura TINA, ha studiato l'applicazione di concetti transazionali rilassati per sistemi distribuiti dove il concetto di 
"transazionalità" deve essere adattato al dominio applicativo. Ultimamente la sua area di competenza si è concentrata sugli 
aspetti architetturali di Java, in particolare per gli aspetti di distrubuted computing, migrazione di codice e mobile agents.
 

 

MokaByte rivista web su Java

MokaByte ricerca nuovi collaboratori
Chi volesse mettersi in contatto con noi può farlo scrivendo a mokainfo@mokabyte.it