Middleware Architecture with Patterns and Frameworks

Chapter 5
Distributed Objects

[© 2003-2009 S. Krakowiak, Creative Commons license][pdf version][ps version]
Organizing a distributed application as a set of objects is a powerful architectural paradigm, which is supported by a number of infrastructures and tools. The development of middleware has in fact started with the advent of distributed object systems. This chapter presents the main patterns related to distributed objects, and illustrates them with two systems that are representative of current technology, Java RMI and CORBA. The main software frameworks used in the implementation of these systems are presented, with reference to open source implementations.

5.1  Distributing Objects

Organizing a distributed application as a (dynamically evolving) set of objects, located on a set of sites and communicating through remote invocations (2.2.2 ), is one of the most important paradigms of distributed computing, known as the distributed objects model. In this section, we first present an overview of this model (5.1.1). We next introduce the main interaction scheme between objects, the remote object call (5.1.2). We finally present the user's view of this mechanism (5.1.3).

5.1.1  Overview

The distributed objects model brings the following expected benefits.
Note that, in the remote objects model, objects are the units of distribution, i.e. an individual object resides on a node in its entirety (Figure 5.1).
Chapters/DistObj/Figs/object-call.gif
Figure 5.1: Distributed objects
Other models for distributing objects have been proposed, such as:
These models may be combined, e.g. fragmented objects may also be replicated, etc.
A distributed application using remote objects is executed as a set of processes located on the nodes of a network. An object's method is executed by a process or a thread (in some models, objects may also be shared between processes), and may include calls to other objects' methods. For such inter-object method calls, three situations may occur (Figure 5.1).
Local invocations are done like in a non-distributed object system. Non-local forms of invocation rely on an object request broker (ORB), a middleware that supports distributed objects. This term has been introduced for the CORBA architecture, but it applies to other object systems as well. An ORB has the following functions.
In the rest of this section, we give a first outline of the operation of a non-local invocation. More details on the internals of an ORB are given in Sections 5.2 and 5.3.

5.1.2  Remote Object Call: a First Outline

An application using remote objects is typically organized using the client-server model: a process or thread executing a method of a client object sends a request to a (possibly remote, or out-of-process) server object in order to execute a method of that object.
The overall organization of a method invocation on a remote object, shown on Figure 5.2, is similar to that of an RPC, as described in Chapter 1 . It relies on a stub-skeleton pair. In contrast with RPC, the stub and the skeleton are objects in their own right. Take the example of the call from object C to the remote object D. The stub for D, on client C's site, acts as a local representative, or proxy, of object D. It therefore has the same interface as D. It forwards the call to D's skeleton on D's site, which performs the actual method invocation and returns the results to C, via the stub.
Chapters/DistObj/Figs/proxies.gif
Figure 5.2: Performing non-local calls
In order to be able to forward the invocation, D's stub contains a reference to D (more precisely, to D's skeleton). A reference to an object is a name that allows access to the object (cf. 3.1 ); this name is therefore distribution-aware, i.e. it contains information allowing the object to be located (e.g. network address and port number). Object references are further developed in 5.2.1.
An out-of-process call on the same node (e.g. from object D to object E) could in principle be performed as a remote invocation. However, it is possible to take advantage of the fact that the objects are co-located, using e.g. shared memory. Thus an optimized stub-skeleton, bridging the client and server address spaces, is usually created in that case.
Let us go into the details of a remote method invocation. The client process invokes the method on the local stub of the remote object (recall that the stub has exactly the same interface as the remote object and contains a reference to that object). The stub marshalls the parameters, constructs a request, determines the location of the remote object using its reference, and sends the request to the remote object (more precisely, to the object's skeleton). On the remote object's site, the skeleton performs the same function as the server stub in RPC: unmarshalling parameters, dispatching the call to the address of the invoked method, marshalling returned values, and sending them back to the stub. The stub unmarshalls the returned values and delivers them to the client process, thus completing the call. This is represented on Figure 5.3.
Chapters/DistObj/Figs/object-call1.gif
Figure 5.3: Invoking a remote object
An important difference with RPC is that objects are dynamically created (details in next section).

5.1.3  The User's View

Remote object infrastructures attempt to hide distribution from the user, by providing access and location transparency. However, some aspects of distribution remain visible, e.g. object creation through factories.
According to the encapsulation principle, an object, be it local or remote, is only accessible through an interface. Object interfaces are described by an Interface Description Language (IDL). The function of an IDL for objects is similar to that of the IDL used in RPC systems. A set of IDL descriptions is used
There is no single format for an IDL. The syntax of most IDLs is inspired by that of a programming language. Languages that include interface definitions, such as Java and C#, define their own IDL.
An example of a general purpose IDL is the OMG IDL, used for programming in CORBA. This IDL may be "mapped" on various programming languages by using appropriate generation tools. For example, using the IDL to C++ mapping tools, stubs and skeletons in C++ may be generated from IDL description files. Client and server executable programs may then be generated, much like in an RPC system ().
The main distribution-aware aspect is object creation and location. Creating a remote object cannot be done through the usual object instantiation mechanism, which involves memory allocation and is not directly applicable to a remote node. Creating a remote object is done through an object factory (2.3.2 ), which acts as a server that creates objects of a specified type. The reference of the created object is returned to the client (Figure 5.4 (a)).
Chapters/DistObj/Figs/creation-fig.gif
Figure 5.4: Object creation
An object may also be created at the server's initiative. In that case the server usually registers the object (more precisely, a reference to the object) in a name service, to be later retrieved by the client (Figure 5.4 (b)). This is an instance ot the general binding mechanism described in 3.3.4 .
As a conclusion, a remote object may only be accessed by a client program through a reference, which in turn may be obtained in several ways: as a return parameter of a call, through an object factory, through a name service. The first two ways imply access to existing objects that in turn have to be located; thus, ultimately, retrieving an object relies on a name service. Examples are described in the case studies.

5.2  Remote Object Call: a Closer View

A first outline of the execution of a remote object call is given in Section 5.1.2. A few points need to be further clarified in this scheme.
These points are considered in the rest of this section.

5.2.1  Object References

In Chapter 3 , we mentioned that the two functions of a name (identification and access) have conflicting requirements, which leads to use different forms of names for these functions. We consider here the means of providing access to an object; an information that fulfills this function is called an object reference.
Chapters/DistObj/Figs/raw-refs.gif
Figure 5.5: Object references
Recall that an object may only be accessed through its interface. Therefore an object reference must also refer to the object's interface. Since an object may be remote, the reference must provide all the information that is needed to perform a remote access to the object, i.e. the network location of the object and an access protocol.
Figure 5.5 shows three examples of reference implementations. In the simplest case (a), the location is a network address (e.g. host address and port number). However, this scheme lacks flexibility, since it does not allow the target object to be relocated without changing its reference. Therefore, indirect schemes are usually preferred. In example (b), the reference contains the network address of a server that manages the object (an object adapter), together with an internal identification of the object on this adapter - more on this in Section 5.2.3. To provide additional flexibility (e.g. allowing a reference to remain valid after the shutdown and restart of a server), an additional level of indirection may be introduced. In example (c), the reference contains the network address of a locator for the adapter, together with the identity of the adapter and the internal object identification. This allows the adapter to be transparently relocated on a different site, updating the locator information without changing the reference.
Recall that a stub, i.e. a proxy (2.3.1 ) for a remote object, contains a reference to the object it represents. Actually, the stub itself may be used as a reference. This has the advantage that the reference is now self-contained, i.e. it may be used anywhere to invoke the remote object. In particular, stubs may be used for passing objects as parameters. This aspect is further developed in Section 5.2.4.
Object references may be obtained through several mechanisms.
Two points should be noted regarding object references.
The validity domain of a reference.  
A reference is not usually universally valid in space and time.
In a closed system relying for instance on a local area network using a fixed protocol suite, object references may use a restricted format, in which some elements are implicit (for example the protocols used). Such references may not be exported outside the system. However, if references are to be passed across interconnected systems using different protocols or representations, then their format must accommodate the specification of the variable elements. For example, the Interoperable Object Reference (IOR) format, used in CORBA systems (5.5), allows for communication between objects supported by different ORBs.
In addition, a reference to an object is only valid during the lifetime of the object. When an object is removed, any reference to it becomes invalid.
Reference vs identity.  
Recall that an object reference is not intended to identify an object, but only to provide a means of accessing it. Therefore a reference cannot in general be used as an identifier (e.g. different references may give access to the same object). Object identification must be dealt with separately (e.g. an object may carry a unique identifier as part of its state, and a method may be provided to return this identifier1).
One could imagine including a unique identification for an object in a reference. However, this would defeat the primary purpose of a reference, which is to give access to an object that provides a specified functionality. To understand why, consider the following scenario: Suppose ref is a reference for object O; it contains the network address and port number of a location server, plus a key for O on this server. The key is associated with the actual network location of O (e.g. again network address and port number). Suppose the server that supports O crashes. A fault tolerance mechanism is activated, which updates the location server with a new copy of O (say O1). While O and O1 are different objects, as regards identity, they are equivalent from the client's point of view (provided consistency is indeed ensured), and the reference ref may at different instants give access to either of them.

5.2.2  The Object Invocation Path

Remote object invocation is actually somewhat more complex than described in Section 5.1.2, because of two requirements.
As a result, the overall remote invocation path is organized as shown on Figure 5.6. This is a general picture. More details are provided in the case studies.
Chapters/DistObj/Figs/invoc-path.gif
Figure 5.6: The invocation path and its main interfaces
The motivations for this organization result from the above stated requirements.
On the client side, a new interface is defined inside the stub. While the "upper" interface of the stub is application-specific (since it is identical to that of the remotely called object), this internal interface is generic, i.e. application-independent. This means that both the interface entry points and the format of the data may be standardized. Typical methods provided by the delegate interface are create_request (constructing a invocation request in a standard form) and invoke (actually performing the invocation).
Likewise, the "lower" interface of the stub, connecting it to the network, is generic. An example of a generic operation is SendRequest, whose parameters are the reference of the called object, the name of the method, the description of the parameters of the called method. An example of a standard is GIOP (General Inter-ORB Protocol), further described in Section 5.5.1.
On the server side, the interface transformation between service (as described by a generic ORB interface) and servant (a specific, application-dependent interface) is again done through a server delegate, part of the skeleton. The skeleton itself (and thus the servant) is located through a piece of software called an object adapter (2.3.3 ). While delegates are part of the internal organization of a stub, and are never explicitly seen by a user, adapters are usually visible and may be directly used by applications.

5.2.3  Object Adapters

An adapter performs the following functions.
There are three main ways of installing a servant object on a server. The first way consists of creating the servant, using an object factory. The second way consists of importing the servant from another site (assumed it is available there), by moving or copying it to the server site. The third way consists of constructing the object from its elementary parts, i.e. a state and a set of functions (methods) operating on the state. This latter mode is used when an existing (or legacy) application, not written in object style, must be reused in an object-oriented setting. In that case, the legacy application needs to be "wrapped up", i.e. its contents needs to be encapsulated to make only visible an object-style interface. This is another instance of interface transformation, which is again performed by an adapter (this explains why adapters are also called wrappers).
Several different adapters may coexist on a server, for example to provide different policies for process activation. A servant object may now be identified by providing an identification for its adapter together with an internal identification of the object within the adapter. This information is part of the object's reference.
Chapters/DistObj/Figs/object-call2.gif
Figure 5.7: Invoking a remote object through an adapter
The operation of a remote object invocation may now be refined as shown on Figure 5.7.
Starting from the stub as above, the call is directed to the adapter, providing the internal identification of the servant object within the adapter. The adapter must locate the object (more precisely, the object's skeleton), activate the object if needed, and forward the call to the skeleton. The call is then performed as described in Section 5.1.2.

5.2.4  Parameter Passing

Passing parameters in a remote object system poses two kinds of problems.
The marshalling problem is similar to that found in RPC systems (1.3.2 ). It is solved by creating marshallers and unmarshallers for the elementary types, using a serializable format (a byte sequence) as the intermediary form.
We now consider passing objects as parameters. Since the standard way of calling a method on a remote object is through a reference to it, the usual mode of passing an object as a parameter is by reference (Figure 5.8 (a)). The reference may have any of the formats presented in 5.2.1, including a stub. It needs to be in a serializable form to be copied on the network.
Chapters/DistObj/Figs/obj-param.gif
Figure 5.8: Passing an object as a parameter
If the object being passed is on the site of the calling method (Figure 5.8 (b), then any access to that object involves a remote invocation from the called site to the calling site. If this access does not modify the object, one may consider passing the object by value, i.e. sending a copy of it on the called site (Figure 5.8 (c). However, this implies that the state of the object may indeed be marshalled and copied on the network2. Choosing the mode of passing for a (read-only) local object is a trade-off between the cost of marshalling and sending the copy of the object and the cost of remotely invoking the object from the called site, which depends on the size of the object's state and on the frequency of access. Some aspects of this trade-off are discussed in [Spiegel 1998].

5.3  Inside an Object Request Broker

In this section, we present a detailed view of the internal operation of an object request broker, emphasizing the binding and communication aspects. While this description is inspired by the organization of the Jonathan ORB, we attempt to present a generic view of the structure and operation of an ORB. Specific examples are presented in Sections 5.4 and 5.5.
A thorough treatment of the patterns involved in remote invocation may be found in [Völter et al. 2004].

5.3.1  The Mechanics of Object Invocation

The use of generic (i.e. application-independent) interfaces in the invocation path to a remote object has been motivated in 5.2.2. The main generic interface has been specified by the OMG in the General Inter-ORB Protocol (GIOP). While this specification is independent of the underlying transport layer, its dominant implementation, called IIOP (Internet Inter-ORB Protocol), is built over TCP/IP.
The initial purpose of GIOP was to allow different CORBA implementations to interoperate, leaving each ORB vendor free to choose an internal transport protocol. However, in practice, GIOP (essentially under the IIOP form), is also used for the internal implementation of ORBs (both CORBA and others such as Java RMI).
We do not intend to describe the full GIOP specification (see [OMG 2003]). We only identify the main elements that are needed to understand the basic mechanics of object binding and invocation.
The GIOP specification covers three aspects.
The IIOP specification defines a mapping of GIOP on the TCP/IP transport protocol. In particular, it defines a general format for object references, called Interoperable Object References (IOR).
Eight message types are defined for object invocation, The two most important are Request (from client to server), and Reply (from server to client). The other messages fulfill auxiliary functions such as aborts, error detection, and IOR management.
A Request message includes a reference for the target object, the name of the invoked operation, the parameters (in marshalled form), and, if a reply is expected, an identifier for a reply holder (the endpoint on which the reply is expected). After sending the message, the calling thread is put to wait. At the receiving end, the servant is located using the reference and the request is forwarded to it.
A Reply message, only created if the invoked operation returns a value, contains that value, in marshalled form, together with an identification of the reply holder. When this message is received by the client, the waiting thread is activated and may retrieve the reply from the reply holder.
Note that the Request message may either be created by a stub, itself generated prior to the invocation, or created dynamically by directly putting its parts together at invocation time. Here we only consider the first case; dynamic request creation is examined in Section 5.6.
An outline of the invocation path may be found on Figures 5.9 and 5.10, which describe the client and server sides, respectively. The operation of the transport protocol used by the GIOP layer is not detailed (see Chapter ).
Chapters/DistObj/Figs/send-request.gif
Figure 5.9: Object invocation: client side
Above the GIOP layer, the invocation path goes through a stub (at the client end) and a skeleton (at the server end). The upper interface of the stub and the skeleton is application specific. Inside the stub and the skeleton, it is convenient to define an additional generic (application-independent) interface, to improve code reusability, leaving the application-specific part to a minimum. Therefore, in several ORB organizations, the stub and the skeleton are separated into an (upper) application-specific part (the stub or skeleton proper) and a (lower) application-independent part called a delegate. The interface of the delegate reifies the application-dependent interface, giving an explicit representation of the operation name and the (marshalled) parameters.
Chapters/DistObj/Figs/receive-request.gif
Figure 5.10: Object invocation: server side
An important aspect is locating the servant at the server's end, using the object reference sent in the Request message. This an ORB-specific issue, which is linked to the organization of object references. For example, Java RMI directly uses a [host address, port number] reference, while CORBA goes through an object adapter. More details on servant location are provided in the case studies.

5.3.2  Binding in an ORB

In the previous section, we have described the (statically generated) invocation path in an ORB. The components of this path (stub, skeleton, delegates, transport protocol endpoints) collectively form a binding object between the client and the servant.
Classes for the stub and skeleton of a remotely used object are generated from a description of the interface of this object. This description is expressed in an Interface Description Language (IDL), as explained in Section 5.1.3.
Instances of these classes are created before invocation, using appropriate stub, skeleton and delegate factories. The main information to be provided is the object reference. This is done using the export-bind pattern.
This process is described on Figure 5.11.
Chapters/DistObj/Figs/binding.gif
Figure 5.11: Binding for remote invocation
Specific instances of this process are described in more detail in the case studies.

5.3.3  Introduction to the Case Studies

We illustrate the internal working of an ORB with two case studies, based on open source implementations of Java RMI and CORBA, two widely used middleware systems based on the remote objects model.
We do not intend to give a detailed description of these systems, but to show the application of common design patterns. The two implementations are "personalities" built on top of the Jonathan kernel (3.4 ). Recall that Jonathan provides a framework for communication and binding. The core of both ORB implementations essentially consists of a binding factory, or binder (3.3.2 ), which itself relies on lower level tools and other binders:
A global view of the structure of the ORBs is given on Figure 5.12. Some details such as the use of common resource managers (schedulers, storage allocators) are not shown. A more detailed view is provided for each case study.
Chapters/DistObj/Figs/personalities.gif
Figure 5.12: The common structure of Jonathan ORBs
The structure of the ORB is described in terms of "components" using a self-descriptive graphical formalism (the arrows connect "required" to "provided" interfaces - see Chapter 7  for more details).

5.4  Case Study 1: Jeremie, an Implementation of Java RMI

We present the Jeremie implementation of Java RMI in the Jonathan framework. After a brief introduction (5.4.1), we illustrate the principle of application development in Java RMI, through a simple example (5.4.2). We finally describe the inner working of the implementation (5.4.3).

5.4.1  Introducing Java RMI

Java Remote Method Invocation (RMI) [Wollrath et al. 1996] implements the remote objects model for Java objects. It extends the Java language with the ability to invoke a method on a remote object (a Java object located on a remote site), and to pass Java objects as parameters in method calls.
Since the Java language includes the notion of an interface, there is no need for a separate Interface Description Language (IDL). Stub and skeleton classes are generated from a remote interface description, using a stub generator (rmic).
Programming with remote objects is subject to a few rules of usage, as follows.
Objects may be passed as parameters to methods. Local objects (residing on the caller's site) are passed by value, and must therefore be serializable. Non-local objects are passed by reference, i.e. a stub for the object is transmitted.
Thus, in accordance with the engineering principle discussed in 1.3.4  (see also [Waldo et al. 1997]), the Java RMI programming model does not attempt to be fully transparent, i.e. some modifications must be made to a centralized application when porting it to a distributed environment.
As mentioned in 5.1.3, a remote object system relies on a naming service. In Java RMI, this service is provided by a registry, which allows remote objects to be registered under symbolic names. The data that is registered is actually a reference for the remote object, i.e. a stub.
The registry may be located on any node. It is accessible on both the client and server node through a local interface called Naming. The interaction between Naming and the actual registry is described in 5.4.3.
Chapters/DistObj/Figs/registry.gif
Figure 5.13: The RMI naming registry interface
The symbolic names have the form: rmi://[host name][:portname]/local name (the items between brackets are optional). A server registers references in the registry using bind and rebind and unregisters them using unbind. The client uses lookup to search the registry for a reference of a given name. The list method is used to list a registry's contents. The use of the registry is illustrated in the next section.

5.4.2  Developing an RMI Application

We present the main steps in developing an RMI application. Since emphasis here is on the internal working of the RMI system, we do not attempt to discuss a realistic application; we use the minimal "Hello World" example.
The centralized version of this example is presented below (the programs are self-explanatory).
// Hello Interface                   // Hello Implementation                              
     public interface Hello {            class HelloImpl implements Hello {    
       String sayHello();}                 HelloImpl() {   // constructor
     };                                      };
                                           public String sayHello() {
                                             return "Hello World!";
                                             }; 
                                         }
// Hello Usage
     ...                               
     Hello hello = new HelloImpl ();   
     hello.sayHello();

In the distributed version, the client and the server run on possibly different machines. In order to allow this new mode of operation, two main problems must be solved: the client must find the location of the hello object (the target object of the invocation); the client must access the target object remotely.
Here is an outline of the distributed version (some details are omitted, e.g. catching exceptions, etc.). First the interface.
   public interface Hello extends Remote {
      String sayHello() throws RemoteException;
   }

The server program contains the implementation of the Hello interface and the main program. Objects are located through a naming service (the registry), which is part of the RMI environment. The server creates the target object and registers it under a symbolic name (5.4.1). The rebind operation does this, superseding any previous associations of the name. Note that the URL prefix of the name is jrmi (for the Jeremie version of RMI)
   class HelloImpl extends UnicastRemoteObject implements Hello {
       HelloImpl() throws RemoteException {
       };
     public String sayHello() throws RemoteException {
         return "Hello World!";
     };

   public class Server {
     public static void main (...) {
       ...
       Naming.rebind("jrmi://" + registryHost + "/helloobj", new HelloImpl());
       System.out.println("Hello Server ready !");
     }
   }

The client program looks up the symbolic name, and retrieves a stub for the target object, which allows it to perform the remote invocation. The client and server must agree on the symbolic name (how this agreement is achieved is not examined here).
   ...
   Hello obj = (Hello) Naming.lookup("jrmi://" + registryHost + "/helloobj");
   System.out.println(obj.sayHello());

The registry may itself be a remote service (i.e. running on a machine different from that of the client and the server). Therefore, both the client and server use a local representative of the registry, called Naming, which locates and calls the actual registry, as described in 5.4.1. This allows the registry to be relocated without modifying the application.
The interaction diagram shown on Figure 5.14 gives a high-level view of the global interaction between the client, the server, and the registry. A more detailed view is presented in the following sections.
Chapters/DistObj/Figs/hello-global.gif
Figure 5.14: The "Hello World" application: overview
The actual execution involves the following steps.
  1. Generate the stub and skeleton classes, by compiling the Hello interface definition on the server site, using the stub compiler provided by Jeremie.

  2. Start the registry (by default the registry is located on the server site, but the system may be configured to make the registry reside on a different site).

  3. Start the server.

  4. Start the client.

5.4.3  The Inner Working of Jeremie

The execution of the above application proceeds as follows: on the server side, export the servant object and generate the actual stub and skeleton objects (the stub compiler has only generated the corresponding classes); on the client side, set up the binding, i.e. the actual connection between client and server, and perform the call. We examine the detail of these operations in turn.

Exporting the Servant Object

Recall that the servant object's class HelloImpl inherits from the standard class UnicastRemoteObject. The goal of this extension is essentially to enhance the creation of a remote object: when new is called on such a class, a skeleton and a stub are created in addition to the actual instance (using the classes generated by rmic), and the stub is returned as the result of new, to serve as a reference for the object. Thus the instruction
Naming.rebind("jrmi://" + registryHost + "/helloobj", new HelloImpl());

in the server program creates a new instance of HelloImpl (the servant), together with a skeleton and a stub for this servant, and returns the stub. It then registers the stub in the naming registry under a symbolic name.
Technically, the creation phase is performed by the UnicastRemoteObject (Figure 5.15), which exports the new servant object impl by calling a servant manager through an exportObject method. This manager (called AdapterContext on the figure) is essentially a driver for an object adapter; it first gets an instance of a stub factory from the ORB, and uses it to create a skeleton skel. It then registers skel into an adapter (essentially a table of objects), in which the skeleton is associated with a key. The key is then exported to the ORB, which delegates its operations to an IIOPBinder. This binder manages the IIOP protocol stack; it encapsulates the key into an object ref, of type SrvIdentifier, which includes a [host, port] pair (the port number was passed as a parameter to exportObject; if missing, an available port is selected by the binder). Thus ref is actually a reference for the servant impl. This reference is then used to create a stub (again using the stub factory), which is finally returned.
Chapters/DistObj/Figs/rmi-export.gif
Figure 5.15: Remote Method Invocation: creating the stub and skeleton
The stub is then registered, which concludes the export phase. Note that the stub must be serializable in order to be transmitted over the network.
This framework is extensible, i.e. it identifies generic interfaces under which various implementations may be plugged in, such as the adapter interface and the binding protocol interface, which are shown on the figure.

Setting up the Binding

The client looks up the servant in the registry, using its symbolic name:
   Hello obj = (Hello) Naming.lookup("jrmi://" + registryHost + "/helloobj");

This operation apparently retrieves a "servant", but what it actually does is setting up the binding, by first retrieving a stub and then binding this stub to the skeleton, and therefore to the actual servant. We now present the details of this operation (Figure 5.16).
Chapters/DistObj/Figs/rmi-bind.gif
Figure 5.16: Remote Method Invocation: retrieving the stub on the client side
The stub object stored in the registry is returned as a result of the lookup operation. This stub is still unbound, i.e. the calling path to the remote servant is not set up. The binding operation relies on the mechanisms of object transmission in Java:
  • when an externalizable object is read from an ObjectInputStream by the readObject method, this method is superseded by a specific ReadExternal method;

  • after an object has been de-serialized, a readResolve method is called on this object.

The first mechanism is used when the stub delegate (here called RefImpl) is read. Recall that the delegate contains a reference to the remote servant, including its IP address, port number and key in the adapter. The call to ReadExternal creates a client endpoint (called CltId on the figure), managed by the IIOPBinder. The second mechanism invokes the bind() method on this endpoint, which in turn sets up the binding, i.e. the path to the remote object, using the IIOPBinder (to set up a TCP/IP session), and a stub factory (to put the final stub together).

Performing the Call

Finally, the call is performed by the operation
   System.out.println(obj.sayHello());

which actually invokes the sayHello() method on the stub. The skeleton is located using the reference contained in the delegate, the remote call is performed using the IIOP protocol, and the skeleton dispatches the invocation to the sayHello method of the servant. The return value is transmitted using the reverse path.

The Mechanics of Registry Invocation

Recall that the registry may be located on any node and is accessible through a local Naming interface. In order to reach the actual registry, Naming calls a LocateRegistry, giving the symbolic name under which the registry servant has been initialized (e.g. RegistryImpl).
Chapters/DistObj/Figs/registry-impl.gif
Figure 5.17: Invoking a registry method
LocateRegistry uses a binder (here JIOP) to retrieve a stub for RegistryImpl (Figure 5.17). The binding process is identical to that described above (i.e. using IIOPBinder), and details are not shown.
When the registry server is initialized on a host, it creates a RegistryImpl servant on a specified port. A precompiled stub class for this servant (RegistryImpl_Stub, needs to be present on each site using the registry.

5.5  Case Study 2: David, an Implementation of CORBA

We present the David implementation of CORBA in the Jonathan framework. After a brief introduction (5.5.1), we illustrate the principle of application development in CORBA, through a simple example (5.5.2). We finally describe the inner working of the implementation (5.5.3).

5.5.1  Introducing CORBA

The Object Management Group (OMG) is a consortium created in 1989 with the goal of making and promoting standards in the area of distributed applications development. CORBA (Common Object Request Broker Architecture) is one of these standards, covering the area of middleware and services for applications based on distributed objects.
CORBA is intended to allow the cooperation of heterogeneous applications, using different languages and operating environments. To that end, the standard defines a common way of organizing applications based on the remote objects model, a common Interface Definition Language (IDL), and the main components of an object request broker architecture. In addition, a number of common services are specified for such functions as naming, trading, transactions, persistence, security, etc. These services are accessible through IDL interfaces.
The global organization of an ORB, as defined by CORBA, is represented on Figure 5.18.
Chapters/DistObj/Figs/corba-archi.gif
Figure 5.18: The global organization of CORBA
The usual invocation path from a client application to a method provided by a remote servant is through a stub and a skeleton generated from an interface description. CORBA defines a generic IDL, from which stubs and skeletons may be created for different languages, using the appropriate tools. The mapping of the entities defined in the IDL to constructs in a particular language is defined by a set of binding rules. Such bindings have been defined for all usual programming languages. Thus for instance a client written in C++ may call a servant written in Java: the client stub is compiled using tools based on the IDL to C++ binding, while the skeleton is compiled using tools based on the IDL to Java binding.
The ORB itself provides interfaces to both clients and servants, in order to fulfill a number of basic functions such as a primitive name service and a tool to map object references to strings and vice-versa, in order to facilitate the interchange of references.
CORBA uses object adapters on the server side. The motivation for an adapter has been given in 5.2.3. The adapter manages servant objects, using an implementation repository. The use of adapters in CORBA is further developed in 5.6.
Finally, CORBA provides facilities for dynamically constructing the elements of an invocation path, by allowing a client application to discover a servant interface at run time (using an interface repository), and to create requests without using stub generation tools. These aspects are examined in 5.6.

5.5.2  Developing a CORBA Application

We use the same application (HelloWorld) as in Java RMI. The centralized version has been described in Section 5.4.2.
The IDL description of the interface of the remote object is:
interface Hello {
   string sayHello();
};

Since we intend to write the client and server programs in Java, this description must be compiled using an IDL to Java compiler, to generate the stub and skeleton files (_HelloStub.java and _HelloImplBase.java, respectively). The compiler also generates auxiliary programs (holders and helpers), whose function will be explained when necessary.
Here is the the program of the server.
import org.omg.CORBA.ORB;
import org.omg.CosNaming.NamingContext;
import org.omg.CosNaming.NamingContextHelper;
import org.omg.CosNaming.NameComponent;
import org.objectweb.david.libs.helpers.IORHelpers;
import idl.*;

class HelloImpl extends _HelloImplBase {

   HelloImpl() {}
   public String sayHello() {
      return "Hello World!";
   }
}

public class Server {
   public static void main (String[] args) {
      try {
         ORB orb = ORB.init(args,null);
         Hello hello = new HelloImpl();
         orb.connect(hello);
         IORHelpers.writeIORToFile(orb.object_to_string(hello),"hello_ior");
         org.omg.CORBA.Object ns_ref =
            orb.resolve_initial_references("NameService");
         NamingContext ns = NamingContextHelper.narrow(ns_ref);
         ns.rebind(new NameComponent[] { new NameComponent("helloobj","") },hello);
         System.out.println("Hello Server ready");
         orb.run();
      } catch (Exception e) {
         System.err.println("Hello Server exception");
         e.printStackTrace();
      }
   }
}

As may be expected, the program of the servant object is identical to that used in Java RMI. The difference lies in the server program.
The first instruction ORB orb = ORB.init(args,null) initializes the ORB, which is thereafter accessible as an object orb. Then an instance hello of the servant is created. The instruction orb.connect(hello) makes the servant known to the ORB for further use, by exporting it to an adapter.
The following instructions register the servant (actually a reference to it) in the CORBA name service. A reference to this service must first be retrieved, which is done using a primitive name service (resolve_initial_references) provided by the ORB. Note the use of the narrow operation performed by a "helper" program: the primitive name service returns references to type Object, and these references need to be cast to the actual type of the object. However, a language cast operation cannot be used here, because the references are not language references, but remote references managed by the ORB.
Finally, the orb.run() operations puts the server thread to sleep, waiting for client invocations.
Here is the client program.
import org.omg.CORBA.ORB;
import org.omg.CosNaming.NamingContext;
import org.omg.CosNaming.NamingContextHelper;
import org.omg.CosNaming.NameComponent;
import idl.*;

public class Client {
   public static void main(String[] args) {      
      try {
         ORB orb = ORB.init(args,null);
         org.omg.CORBA.Object ns_ref = 
            orb.resolve_initial_references("NameService");
         NamingContext ns = NamingContextHelper.narrow(ns_ref);
         org.omg.CORBA.Object obj_ref =
            ns.resolve(new NameComponent[] { new NameComponent("helloobj","") });
         Hello obj = HelloHelper.narrow(obj_ref);
         System.out.println(obj.sayHello());
      } catch (Exception e) {
         System.err.println("Hello Client exception");
         e.printStackTrace();
      }
   }
}

The ORB is initialized like in the server program. Then a reference for the servant is retrieved using the naming service. This reference must again be cast to its actual type, using the appropriate helper. Finally the remote operation is invoked.
CORBA uses a standard object reference format (Interoperable Object Reference, or IOR), defined by the IIOP specification. While these references are opaque (i.e. their contents is hidden), they may be used through primitives provided by the ORB. Thus, in the server program, the registration of the servant in the name service might be replaced by:
IORHelpers.writeIORToFile(orb.object_to_string(hello),"hello_ior_srv");

This instruction writes in the hello_ior_srv file a "stringified" form of the IOR. This string may be sent to the client (e.g. by mail), and copied in a file hello_ior_clt. The reference to the servant may then be retrieved in the client program by:
hello = orb.string_to_object(IORHelpers.readIORFromFile("hello_ior_clt"));

Executing the application involves exactly the same steps as in Java RMI: generating the stub and skeleton classes by compiling the IDL interface description; starting the name service; starting the server; starting the client.
CORBA allows additional facilities: dynamic request generation, adapter programming. These aspects are examined in Section 5.6.

5.5.3  The Inner Working of David

The compilation of the IDL interface description generates several files for each servant. For instance, from the Hello interface description, the Idl2Java compiler creates the Java source files for the following entities: HelloHelper, a class which carries auxiliary operations such as narrow (the reference casting operation); HelloHolder, a class which carries read and write operations of objects of type Hello on streams; HelloOperations, the interface of objects of type Hello; _HelloStub, the stub class; _HelloImplBase, the base class from which the servant object derives (this class extends a predefined Skeleton class).
The overall working of the CORBA ORB is similar to that described for Java RMI. The main differences are the use of the adapter on the server side, and the reflective operations described in 5.6.
The first step is to obtain a reference on the ORB as an object. This is done by the operation ORB orb = ORB.init(), which creates an ORB as an instance of a singleton class (a class with a single instance). This ORB provides a number of primitive operations, some of which are described in the rest of this section.

Exporting the Servant Object

The servant object's class HelloImpl inherits from the _HelloImplBase generated class, which itself inherits from Skeleton. This extends the servant's class constructor: when a new servant is created (an instance hello of HelloImpl), a new server delegate (5.3.1) is created and associated with that servant.
The operation orb.connect(hello) exports the new servant to the ORB. The server delegate is registered in the adapter (which returns a key to subsequently retrieve the servant), and then exported to the IIOP binder (which provides host address and port). At this stage, the IOR (containing host, port, and key) is ready, and it is actually registered in the servant itself (here, as an instance of SrvIdentifier). This is shown on Figure 5.19.
Chapters/DistObj/Figs/corba-export-1.gif
Figure 5.19: Exporting a servant in CORBA, phase 1
The next step consists in registering the servant in the name server. The name server must first be located, which is done as follows through a primitive naming service provided by the ORB:
org.omg.CORBA.Object ns_ref =
    orb.resolve_initial_references("NameService");
NamingContext ns = NamingContextHelper.narrow(ns_ref);

Retrieving a reference for the name server uses an internal association table (a context) managed by the ORB, in which a reference for the server has been initially registered. From this reference, a stub for the name server is generated, using the binding mechanism explained in the next subsection. The function of narrow (reference cast) has been explained in 5.5.2.
The servant is then registered in the name server:
ns.rebind(new NameComponent[] { new NameComponent("helloobj","") },hello);

The effect of this operation is the following (Figure 5.20).
Chapters/DistObj/Figs/corba-export-2.gif
Figure 5.20: Exporting a servant in CORBA, phase 2
The reference of the servant (i.e. the SrvIdentifier) is sent to the name server, using the encapsulation mechanism described in 3.4.5 . In this process, the identifier is unmarshalled from an ObjectInputStream, using the ReadObject operation. This operation invokes bind on the decoded identifier, in the IIOPBinder context, which in turn invokes a JStubFactory. This latter constructs a ClientDelegate (5.3.1), an access point to the servant using a generic (invoke) interface. This delegate is registered in the name server, to be later retrieved by the client, as described below.

Completing the Binding

After initializing the ORB and locating the name server (like above), the client binds to the servant by calling the name server:
ns.resolve(new NameComponent[] { new NameComponent("helloobj","") });
Hello obj = HelloHelper.narrow(obj_ref);

Like in the registration phase, the ClientDelegate is transmitted in an encapsulated form and unmarshalled through ReadObject. The IIOPBinder now constructs a stub using this delegate, using the mechanism illustrated in the lower-right quarter of Figure 5.20. The binding is now complete, including the communication path (session) between the client and the server.

Performing the Call

The call may now proceed using the object invocation scheme described in 5.3.1 and illustrated in Figures 5.9 and 5.10. The mechanics of passing objects as parameters or results by reference is again illustrated by the operation of the Naming.rebind primitive (Figure 5.20, i.e. recreating a delegate on the receiving site as the core of the object's stub.

5.6  Complements and Extensions


Currently not available (should cover reflective features, semi-synchronous invocations, etc.)

5.7  Historical Note

The historical evolution of object middleware has been outlined in the Historical Note section of the Introduction Chapter (1.5 ).

References

[OMG 2003]
OMG (2003). CORBA/IIOP Specification. Object Management Group. http://www.omg.org/technology/documents/formal/corba_iiop.htm.
[Spiegel 1998]
Spiegel, A. (1998). Objects by value: Evaluating the trade-off. In Proceedings Int. Conf. on Parallel and Distributed Computing and Networks (PDCN), pages 542- 548, Brisbane, Australia. IASTED, ACTA Press.
[van Steen et al. 1999]
van Steen, M., Homburg, P., and Tanenbaum, A. (1999). Globe: A Wide-Area Distributed System. IEEE Concurrency, pages 70-78.
[Völter et al. 2004]
Völter, M., Kircher, M., and Zdun, U. (2004). Remoting Patterns: Foundations of Enterprise, Internet, and Realtime Distributed Object Middleware. John Wiley & Sons.
[Waldo et al. 1997]
Waldo, J., Wyant, G., Wollrath, A., and Kendall, S. (1997). A Note on Distributed Computing. In Vitek, J. and Tschudin, C., editors, Mobile Object Systems: Towards the Programmable Internet, volume 1222 of Lecture Notes in Computer Science, pages 49-64. Springer-Verlag.
[Wollrath et al. 1996]
Wollrath, A., Riggs, R., and Waldo, J. (1996). A Distributed Object Model for the Java System. Computing Systems, 9(4):265-290.

Footnotes:

1note that, in this case, we need access to the object in order to check its identity.
2For example, in Java, an object must implement the java.io.Serializable interface to allow its state to be marshalled.


File translated from TEX by TTH, version 3.40.
On 27 Feb 2009, 12:58.