Chapter 2
The Binding Framework

[pdf version]
This document is made available under the terms of the GNU Free Documentation License as published by the Free Software Foundation (http://www.gnu.org/copyleft/fdl.html)

2.1  Principles of the Framework

Jonathan is an open Object Request Broker (ORB). An ORB is a middleware that mediates the interaction between clients and servers in an object-oriented environment. The primary function of an ORB is binding clients to servers. Binding refers to the process through which an object gets access to another object, and also to the result of this process. Since binding is closely related to naming, we first informally define the main notions related to naming in Jonathan.

Jonathan defines special types for different kinds of names, such as the following. A naming context is itself an object, which may be designated by an identifier in another naming context. This allows chains of names to be built, in which the path from an identifier to an object may go through several successive naming contexts. In order to reflect this organization, a name (of class Name) may take the form of a pair (id, subname), in which id is an identifier (represented as a String), and subname is a (possibly null) name. A common organization is a tree-structured context, in which names are identifiers built of strings separated by separators (e.g. a/b/c). The usual conventions apply (e.g. names starting with the separator are absolute, i.e. relative to the root context, and other names are relative to a local naming context).

In a first and very rough approximation, the Jonathan approach to binding may be summarized as follows (a detailed description is given in Section 2.3).

A typical bind-export situation is that of a client-server system. The server exports the interface of a service that it provides. A client binds to this interface, using a name that represents it. This may be done at a low level (e.g. the client knows the IP address of the server and a port number for the service) or at a higher level (e.g. the client calls a trader that locates the service and performs the binding). Examples of the use of binding in a simple client-server protocol may be found in the communication framework tutorial.

In addition to export, unexport and bind, the main operations regarding naming are related to the management of identifiers. Since identifiers may need to be transmitted on a network, they may have to be encoded into a byte array, and decoded upon reception. The operations encode and decode respectively perform the encoding and decoding. While encoding is borne by the Identifier interface, decoding must be borne by each destination naming context.

The last operation on identifiers is resolve. Recall that a name may be a referencing chain, consisting of a sequence of names valid in intermediate naming contexts. The operation id.resolve() returns the next name in the referencing chain, if such a name exists (null otherwise). It may be considered as the dual of export (export adds a name in front of the referencing chain, resolve removes the first name). The use of the export-resolve pair is illustrated in the domain example (2.2).

2.2   Examples

2.2.1  Single object adapter

The simplest possible example of a naming context is the single object adapter (SingleOAdapter). Loosely speaking, an adapter is a registry for servants, which is used at the server side of an ORB (a servant is an object that implements a specific service). In the present case, the adapter manages a single target object. The class of identifiers (SOAIdentifier) managed by SingleOAdapter has a single attribute, the target object; since the identifier is unique, it does not need to have a byte array encoding (Figure 2.1 a). The bind operation simply returns the target object. The export operation creates an SOAIdentifier for the exported target object, and then exports it to the provided naming context by calling export on that context. Since a single object may be exported, export fails if a different object is currently held by the adapter.

2.2.2   Minimal object adapter

The next example describes a slightly more elaborate adapter (MinimalAdapter), which simply manages a table of Java objects (Figure 2.1 b). More precisely, the table contains holders, which contain couples (identifier, object reference). The table is accessed by hashcoding. An identifier contains an integer value (allocated in increasing order), a hashcode, and an 8-byte encoding (the concatenation of the byte representation of value and hashcode).

Figures/Fig1.gif
Figure 2.1: Object adapters

The current implementation includes some optimizations: there is a single holder pool for all adapter instances; holders that have been freed (by unexport) are kept on a reusable list, which reduces the number of holder creations. In order to prevent the holder table from being garbage collected (since no permanent reference points to it), a waiting thread is associated with the table when the first holder is allocated and disconnected when the last holder is freed.

When a new object is exported to a naming context nc, a reusable holder is selected; if none is available, a new holder is created. In both cases, the holder is filled with a new instance id of MOAIdentifier and the reference of the target objet. Then the object is exported to the naming context nc. Its identifier in this context, id_nc, is returned (and also copied in the holder). The unexport operation returns the holder to the reusable list and calls unexport on id_nc (thus recursively unexporting the object from a context chain).

The bind operation on a MoaIdentifier tries to find a matching holder using the hashcode. The operation returns the object reference if a match is found, and returns null otherwise.

2.2.3   Domain

We now consider the implementation of a domain in Jonathan. Domains are used to solve the following problem arising in distributed computing. When an identifier is transmitted over a network, it is encoded (marshalled) on one side and decoded (unmarshalled) on the other side. In order to decode an identifier, one needs to know the naming context in which it has been encoded (because decode is borne by that context). If multiple protocols coexist, each protocol has its own naming context. A domain is used to manage these different contexts: it is a naming context that is used to identify other naming contexts and to wrap their identifiers in its own identifiers.

In JDomain, the standard implementation of a domain in Jonathan, each different naming context is associated with an integer. The main data structure is a linked list of holders. A holder contains an integer value (jid), a reference to a naming context, and the type (i.e. the name of the Java class) of this context. When a domain is created, this list is empty. An initial context (whose value is specified in the configuration phase) describes the predefined associations between binder classes and integer values.

Figures/Fig2.gif
Figure 2.2: The implementation of a domain in Jonathan

We describe the operations of JDomain in some detail because they provide a good practical illustration of the main concepts related to identifiers in Jonathan. We first describe the format of the identifiers managed by JDomain, which are instances of class JId. A JId encapsulates the identifier id, the integer value jid associated with the identifier's context, and an encoding of the pair (jid, id). Depending on how it was generated, a JId may actually contain (jid, id) or (jid, encoding). As is shown later, id can be generated from encoding and vice versa, if jid is known.
A summary of the main operations follows.

An illustration of the use of JDomain for marshalling and unmarshalling identifiers is given in Section 3.

2.2.4   Protocol

The last example is an overview of the Protocol interface in Jonathan, an abstraction of a communication protocol. A protocol provides a naming context that only deals with a specific family of interfaces - called sessions - and manages names - called session identifiers - to designate these interfaces. The actual naming context associated with a protocol is called a ProtocolGraph: a protocol graph describes a rooted acyclic structure composed of one or several communication protocols, e.g. a protocol stack.

A session is an interface (of type Session_Low ) to which invocations (e.g. service requests) may be sent using the send method. Typically, a session is created, through an export operation, when a server is made available; the session thus represents the interface of the server. The export operation returns a session identifier (a name for the session), which can be transmitted over the network and reconstructed on another host. In order to access the server interface, a remote host must call bind on an identifier that designates this interface. The bind operation sets up a communication channel to the server (the details of this operation are protocol-dependent) and returns an interface of type Session_High, through which the server may be invoked (by the send method).

Figures/Fig3.gif
Figure 2.3: Binding in a client-server protocol

A detailed description of protocols may be found in the communication framework tutorial

2.3   Binding Objects and Binding Factories

2.3.1  Binding Objects

Recall that the main function of an ORB is to bind clients to (usually remote) servers. Clients and servers are represented by objects. If two objects O1 and O2 belong to the same address space, O1 can directly invoke O2 through its reference (an address in memory). If O1 and O2 belong to different address spaces, on possibly heterogeneous platforms, the invocation uses a chain of intermediate objects. The Reference Model of Open Distributed Processing (RM-ODP), on which Jonathan is based, defines the notion of a binding object to represent this situation. The simplest form of a binding object of a direct reference to the object (e.g. an address). The general form is a composite, distributed object that encapsulates the chain of intermediate entities through which the target object may be reached. For instance, in the case of a remote method call, the binding object encapsulates the client stub, the bidirectional communication channel used to transmit messages between the client and the server, and the server stub (or skeleton). A binding object may take different forms, to represent different kinds of bindings (e.g. one to one, one to many, group communication, etc.). A binding object has a local representative in the address space of each of the objects that it binds; thus interaction between a client or server object and the binding object is purely local. All remote communication takes place within the binding object.

Figures/Fig4.gif
Figure 2.4: Binding object

A binding object has a type, which represents the set of properties associated with the binding: protocols used, quality of service constraints, etc.

2.3.2   Binding Factories

Recall the difference between naming and binding: a name identifies an object, while a binding, in addition, gives effective access to the object. Thus a binding is a stronger form of a name. Similarly, a binding factory (also called a binder) is a special form of a naming context, which can create binding objects. Thus a binder implements the bind operation (although, as mentioned above, this operation may be borne either by the binder itself or by the identifiers that it manages). A binder also implements the export operation, which returns a handle (usually an identifier) in a specified naming context for an object managed by the binder. If there exists a chain of contexts between that naming context and the binder, the export operation is recursively called, and walks through this context chain.

The two adapters examined in Section 2.1 (SingleOAdapter and MinimalAdapter) are a simple form of binders, since the identifiers that they manage give direct access to the target object (i.e. the bind method on an identifier returns the target object, not a surrogate for it). JDomain is a binder that manages other binders, and invokes them when needed.

More elaborate forms of binders may be found in Jonathan.

The common pattern for binders consists of the following elements. A binder that manages objects usually provides one identifier type. For example, MOAImpl provides MOAIdentifiers. On the other hand, a binder associated with a protocol usually contains two types of identifiers, respectively associated with servers and clients. For example, IIOPBinder provides SrvIdentifiers and CltIdentifiers. An SrvIdentifier designates an exported session (a channel to a server object), while a CltIdentifier is a distribution-aware name for a possibly remote server objet (``distribution-aware'' means that the name is created using the location parameters of the server object, i.e. host, port, key in local context). Calling bind on a ClientIdentifier returns a stub for the server object (and possibly returns the object itself if it happens to be local).

A generic description of export and bind follows.

[in Binder]

Identifier export (Object obj, Context hints){
   create new identifier id (using binder's identifier)
   create new element in context table with (id, obj)
   possibly export obj to other context
   possibly create new session
   return id (or last identifier created)
   }

[in Identifier]

Object bind (parameters){
   case of
     local object:
        lookup target identifier in context table;
        if (found)
             {return associated object}
     remote object:
        determine session from target identifier (or create it if needed)
        create stub with this session and parameters
        return stub
   }

Specific instances of export and bind are described in detail in the next sections.

2.4  Use Case1: Jeremie, an RMI-like Personality

The Jeremie personality of Jonathan implements an RMI-like ORB. The general organization of Jeremie is described in the Jeremie tutorial [not yet available]. In this section, we present a simple example showing how binding is performed in Jeremie, using the ``Hello World'' application.

2.4.1  Overview

A centralized (single machine) version of the ``Hello World'' application is shown below. The programs are self-explanatory.

  Hello Interface                              Hello Implementation
                                  
     public interface Hello {                     class HelloImpl implements Hello {     
       String sayHello();}                           HelloImpl() {   // constructor
     };                                              };
                                                   public String sayHello() {
  Hello Usage                                          return "Hello World!";
     ...                                             };
     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.

Remote access is achieved by providing appropriate extensions to the interface and class of the target object:

   public interface Hello extends Remote {
      String sayHello() throws RemoteException;
   }
   class HelloImpl extends UnicastRemoteObject implements Hello {
      HelloImpl() throws RemoteException {
      }
      ...
   }

Location is achieved through a naming service (the registry), which is part of the RMI environment. The server identifies the target object by registering it under a symbolic name (the name has the format of an URI). The rebind operation does this, superseding any previous associations of the name.

   Naming.rebind("jrmi://" + registryHost + "/helloobj", new HelloImpl());}

The client looks up the name, and retrieves the target object (it actually retrieves a local representative for the object, i.e. a stub; this is explained in detail later on).

   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 the registry through a LocateRegistry service, using the following scheme.

   Naming.rebind}
      Registry registry = LocateRegistry.getRegistry(host, port);}
      registry.rebind(name, obj);}
     ...
   Naming.lookup}
      Registry registry = LocateRegistry.getRegistry(host, port);}
      registry.lookup(name, obj);}
      ...

The values of host, port, name are obtained by parsing the URI jrmi://host:port/name; default values for [host, port] are [localhost, 12340]. The interaction diagram shown on Figure 2.5 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.

Figures/Fig5.gif
Figure 2.5: The Jeremie ``Hello World'' application: overview

Recall that distributed invocation uses a stub-skeleton scheme, in which the stub (resp. the skeleton) acts as a local representative of the server on the client site (resp. of the client on the server site). The following figure shows a refined form of the previous interaction diagram, showing how the stubs are transmitted (skeletons are not shown, since they do not move).

Figures/Fig6.gif
Figure 2.6: The Jeremie ``Hello World'' application: stub transmission

We shall now explain in detail how this interaction scheme is supported by Jeremie, the RMI personality of Jonathan. The interaction takes place in four steps.

  • Step 1: Compile interfaces, producing stub and skeleton classes
  • Step 2: Create binding objects; this involves the generation of instances of stub and skeleton classes for HelloImpl and for Registry
  • Step 3: Get stubs

    1. for the server:

      • invoke LocateRegistry.getRegistry: get stub for Registry
      • invoke registry.rebind (HelloImpl, name): send stub to Registry
    2. for the client:

      • invoke LocateRegistry.getRegistry: get stub for Registry
      • invoke registry.lookup (name): get stub for HelloImpl
  • Step 4: Call service: client invokes HelloImpl using stub.

The following sections present these four steps in detail.

2.4.2   Step 1: Stub and Skeleton Generation

[***description of jrmic to be provided]

Figures/Fig7.gif
Figure 2.7: Generation of stub and skeleton classes

2.4.3   Step 2: Binding Object Creation

Overview.   As described in section 2, a binding object for remote interaction has three main parts: stub, skeleton, and communication channel. Creating a binding object means instantiating these three parts and linking them together, which is done in steps 2 and 3. In step 2 (export), instances of stubs and skeleton are created at the server's site using the classes created in step 1, and resources for communication channels (ports and sockets) are prepared. In step 3 (bind), instances of stubs are acquired by the clients, thus effectively creating the binding object.

Stub and skeleton instantiation is essentially an application of the export mechanism. In order to allow an object to be accessed remotely, the basic device is to make its class extend a predefined class called UnicastRemoteObject. This class is associated with a local naming context, an object adapter and a binder. Two versions of UnicastRemoteObject are available, one using MOAContext and MinimalAdapter, the other one using SOAContext and SingleOAdapter. In both cases, the binder is JIOP.

Calling the new method on HelloImpl, an extension of (MOA) UnicastRemoteObject, not only creates a new instance of HelloImpl, but also the main components of the binding object that will later (step 3) be instantiated through bind: a stub, a skeleton, and the communication channel that connects them. The stub is returned as a result of new. This process relies on an iterated use of export; it is outlined on Figure 2.8.

Figures/Fig8.gif
Figure 2.8: Generation of stub and skeleton instances

The stub and skeleton are created by StdStubFactory, an instance of which is delivered by the JIOP binder. JIOP delegates its function to the underlying IIOPBinder, which exports an identifier for the communication channel, as an instance of SrvIdentifier (described in more detail below), to be included in the stub. The actual channel will be created in step 3 through a bind operation on JIOP.

The instantiation of stub and skeleton for RegistryImpl (implementation of Registry) follows a similar scheme, since RegistryImpl also extends UnicastRemoteObject. The main difference is that RegistryImpl uses SingleOAdapter and SOAContext instead of MinimalAdapter and MOAContext, respectively, because there is a single object to manage, namely the registry.

Stub and skeleton instantiation.   Stubs and skeletons are instantiated by a stub factory using the stub and skeleton classes generated in step 1.

The skeleton is generated by a call to newRequestSession in StdStubFactory. This method tries to load the skeleton class (a class whose name is, by convention, < target objet class name > _Skel, e.g. HelloImpl_Skel, and then creates an instance of it. If the skeleton class cannot be found, a generic skeleton is created (an instance of a protected class RequestSessionImpl). Note that a skeleton class implements RequestSession, which is a generic interface for the recipient of a remote invocation.

The skeleton is then exported to the adapter (an instance of MinimalAdapter, described in Section 1), and then to JIOP, which delegates to IIOPBinder. Each export adds a new level of identifier encapsulation. Ultimately, the identifier delivered by IIOPBinder has the form:

SrvIdentifier[ moa-id [ session ], host, port]

in which session represents the skeleton for the target object. Note that the identifier includes all data needed to set up a remote communication using the IIOP protocol. If port was not specified (i.e. was 0), an available port number is selected.

The stub is generated by a call to newStub in StdStubFactory. This method returns a stub (an instance of RemoteStub) that represents a remote object. It has two forms, that differ by their parameters:

  • newStub(Object impl, Identifier id). This form is used to create a new stub for a target object impl. The parameter id is an identifier of the object, to be included in the stub for future use by bind. id is the identifier delivered by IIOPBinder. As noted above, it encapsulates all the parameters needed to access the remote object, i.e. host, port, adapter, local identifier in adapter.
  • newStub(SessionIdentifier ep, Identifier [] ids, Context hints). This form is used to create a stub for a remote object that is already accessible through a SessionIdentifier, i.e. a name representing a session in the context of a protocol.
Typically, the first form of newStub is used when a stub is created on the site of the target object. What is actually created is a stub prototype, from which the actual stub (on the client site) is later derived in the binding step, using the second form of newStub, as explained in step 3.

Taking a closer look at stub creation, a central operation is the creation of a reference to the remote object, which is embedded in the stub. This reference, an instance of RefImpl, implements the RemoteRef interface. It includes the informations that allow the remote object to be invoked, and a reference to a marshaller factory, which provides operations for parameter marshalling and unmarshalling (this aspect is developed in the communication framework tutorial). The RemoteRef interface extends Externalizable and Serializable, which allows references to be transmitted on a network (this aspect is developed in step 3).

IIOPBinder: a closer look.   IIOP is a protocol stack (GIOP on top of TCP-IP), managed by GIOPProtocol (details in Section 2.5). The session identifiers are instances of GIOPSessionIdentifier. The main data structure of IIOPBinder is a context table that lists the target objects currently known (i.e. exported) by the binder. Each target is identified by its naming context, a GIOP session identifier (a channel to get to the target), and a port number. Since a target may have been exported several times, a counter is incremented on export and decremented on unexport. When the counter reaches 0, the entry is removed from the table.

Identifiers of target objects in IIOPBinder (instances of SrvIdentifier) are created by export(Object id, Context hints), where id is an identifier that designates (in an adapter) the interface of the object to be exported; hints may contain a preferred port number. The adapter is first exported to the context table, i.e. looked up in the table and inserted there if it is absent or if the preferred port number is not found. In that last case, a new IIOP protocol stack (GIOP over TCP-IP) is created, with the preferred port (or a new port if no preferred port has been supplied). This is done by an export operation on the IIOP stack, which returns a GIOPSessionIdentifier. Finally IIOPBinder returns a SrvIdentifier that encapsulates id, the adapter, the session_id and port number.

The bind operation of IIOPBinder is described in the next section.

2.4.4   Step 3: Binding

In this phase, the stubs instantiated previously are actually acquired by clients. After the server has created an instance of HelloImpl, together with the corresponding stub and skeleton, it calls Naming.rebind(HelloImpl, name), as shown on the upper right-hand side of Figure 2.10. Naming (the local interface of the registry) calls LocateRegistry in order to acquire a stub for RegistryImpl. It then uses this stub to perform the actual call. We now go through the details of this process (Figure 2.9).

In order to create a binding to the registry, LocateRegistry invokes the bind operation on the JIOP binder, using the symbolic name, hostname and port number of the registry. Like in the export step, JIOP delegates this operation to IIOPBinder, which creates a new session (calling GIOPProtocol) and returns a stub for RegistryImpl using this session (calling StdStubFactory). The rebind call is performed on this stub.

Figures/Fig9.gif
Figure 2.9: Rebinding stub into registry

We now go through the details of this call: rebind(HelloImpl, name), which is summarized on Figure 2.10. Recall that HelloImpl is actually a stub (as returned in step 3 by the new operation on the HelloImpl class) . In order to be sent on the network, the value of the stub is passed to a Marshaller, which in turns invokes writeObject on the outgoing ObjectOutputStream (a class provided by the java.io library). The specifications of this class allow writeObject to be superseded by a specific user-provided writeExternal operation. Such an operation is actually defined in the RefImpl class (the remote reference embedded in the stub); it uses JDomain (as explained in Section 2.2) to encode the identifiers of RefImpl. The encoded identifiers are sent on the network.

Figures/Fig10.gif
Figure 2.10: Sending stub to registry

At the receiving end (the registry), the reverse process takes place; it is triggered by the message receipt, which makes the RegistryImpl skeleton call Unmarshaller.readValue. When the stub is unmarshalled, the readObject operation on the ObjectInputStream is superseded by a readExternal method provided by JDomain. In addition, readObject automatically invokes a readResolve method provided by StdStub (the parent class of the stub), which delivers the actual stub, through a call to the bind method of IIOPBinder. The aim of this operation is to complete the binding if needed; in the present case, there is no additional work to do (since the stub contains a reference to the target object), and the HelloImpl stub received through the network is delivered to the skeleton and henceforth to RegistryImpl (the registry).

The Naming.lookup method invoked by the client works in a similar way. The HelloImpl stub, previously recorded in the registry, is retrieved by its name and delivered to the client through the same sequence (i.e. marshall, encode, decode, unmarshall and resolve).

The overall binding process may be summarized as follows:

  • The server (using the export method of a binder) creates an instance of the target (HelloImpl) object, together with a skeleton and a template of the stub, on the server site;
  • The server (using rebind, which invokes the bind method of a binder) creates a session; it then creates a stub using this session, sends it to the registry, and records it there under a symbolic name;
  • The client (using lookup, which again invokes the bind method of a binder) retrieves the stub from the registry by its symbolic name and creates the actual stub on the client site.
The remote invocation may now take place. It is described in the next section.

2.4.5   Step 4: Invocation

The invocation of the remote object is performed using the invoke method of the RefImpl instance contained in the stub. Recall that RefImpl also contains a reference to a session (an instance of Session_High), created by the GIOPProtocol in the binding phase. This session in turn relies on the underlying protocol (TcpIpProtocol) to actually send the message, using IpConnections (which encapsulate sockets). This mechanism is described in detail in the communication framework tutorial. For completeness, we summarize it on Figure 2.11.

Figures/Fig11.gif
Figure 2.11: Performing remote invocation

This figure represents the call of the method sayHello (from client to server through stub and skeleton). The return of the result (the string ``Hello World!'') follows a similar scheme and is not represented here.

2.5   Use Case 2: David, a CORBA Personality

[*** to be provided ***]




File translated from TEX by TTH, version 3.05.
On 11 Jun 2002, 11:49.