
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.
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 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.
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).
|
|
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.
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.
|
|
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.
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).
|
|
A detailed description of protocols may be found in the communication framework tutorial
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.
|
|
A binding object has a type, which represents the set of properties associated with the binding: protocols used, quality of service constraints, etc.
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.
A generic description of export and bind follows.
Specific instances of export and bind are described in
detail in the next sections.
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.
A centralized (single machine) version of the ``Hello World'' application
is shown below. The programs are self-explanatory.
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:
Remote access is achieved by providing appropriate extensions to the interface
and class of the target object:
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.
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).
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.
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.
[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
}
2.4 Use Case1: Jeremie, an RMI-like Personality
2.4.1 Overview
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();
...
public interface Hello extends Remote {
String sayHello() throws RemoteException;
}
class HelloImpl extends UnicastRemoteObject implements Hello {
HelloImpl() throws RemoteException {
}
...
}
Naming.rebind("jrmi://" + registryHost + "/helloobj", new HelloImpl());}
Hello obj = (Hello) Naming.lookup("jrmi://" + registryHost + "/helloobj");}
System.out.println(obj.sayHello());}
Naming.rebind}
Registry registry = LocateRegistry.getRegistry(host, port);}
registry.rebind(name, obj);}
...
Naming.lookup}
Registry registry = LocateRegistry.getRegistry(host, port);}
registry.lookup(name, obj);}
...
|
|
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).
|
|
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.
The following sections present these four steps in detail.
[***description of jrmic to be provided]
|
|
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.
|
|
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:
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.
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.
|
|
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.
|
|
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 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.
|
|
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.
[*** to be provided ***]