CORBA detailed

An applications, client and server, have to initialize ORB by calling an ORB API method, ORB_init. It initializes the ORB and returns a handle of the CORBA::ORB pseudo-object.

CORBA pseudo-objects

Not all objects that can be accessed through the ORB are actual objects that are connected to the bus. CORBA ORB implements a number of pseudo-objects that are emulated by the ORB.

The following are some of the ORB pseudo-objects:
 
CORBA::ORB Provides many methods supporting distributed objects.
CORBA::Object Provides general methods that can be called for any object. Every objects inherits methods from CORBA::Object.
CORBA::BOA A pseudo-object that provides an interface to the Basic Object Adapter.
CORBA::Request Used to create dynamic invocation requests.
CORBA::NVList Used to create parameter lists for dynamic invocation requests.

CORBA Services

CORBA services are accessible through pseudo-objects. The services are accessed through the list_initial_services and resolve_initial_references methods of the pseudo-object CORBA::ORB. For example, if a handler of the object providing the Naming Service is obtained, then the objects may request the Name Server to find remote objects.

The following table describes the CORBA services:
 
Service Purpose
Life Cycle Management of objects.
Persistence Storage for objects.
Naming White Pages for objects.
Event Object notification on occurrence of specific events.
Concurrency Locking on behalf of transactions or threads.
Transaction Support for transactions between objects.
Relationship Management of object relationships that can be created dynamically.
Externalization Streaming data in and out of components.
Query A subset of SQL providing query operations for objects.
Licensing Billing for the use of objects.
Properties Management of object properties that can be dynamically associated with objects.
Time Time synchronization and timeout services.
Security Authentication, access control lists, confidentiality for distributed objects.
Trader Yellow Pages for objects.
Collection Creation and manipulation of common collections for CORBA interfaces.
Startup Allows requests to automatically start up when an ORB is invoked.
CORBA also specifies a number of Common Facilities that provide the application programmers with higher level frameworks encoded in IDL. At even a higher level of abstraction, Business Objects, standard CORBA components, can be used to create network applications in unpredictable ways.

Internet Inter-ORB Protocol (IIOP)

There are many vendors providing ORBs. The implementation is up to the maker, but the CORBA specifications must be satisfied if the ORB wants to be called CORBA-compliant. Internet Inter-ORB Protocol (IIOP) is the critical element of the architecture that delivers a true inter-operability. IIOP is based on TCP/IP. It allows for communication between objects that reside on ORBs of various vendors.

CORBA allows that other protocols may be used in addition to IIOP. Accordingly, they are called Environment-Specific Inter-ORB Protocols (ESIOPs). DCE ESIOP is one of such optional protocols that provide a bridge to the DCE components.

CORBA server

A CORBA server is a process that manages a number of objects. It does it in cooperation with Basic Object Adapter (BOA), which is another CORBA pseudo-object (CORBA::BOA). Together they give the clients an illusion that every possible object is ready for fulfilling incoming requests. In fact, many objects are active only when they are being used. Otherwise, the servers would have to be huge to handle all of the objects at the same time. There are a number of ways, in which objects can be managed by the server.

The following table provides their description:
 
Shared server Objects run inside one server process as threads.
Unshared server Each object resides on a separate process server.
Server-per-method Each method is run within a separate server process, so several servers might be running for the same object.
Persistent server The server is started outside of the CORBA facilities, but otherwise it behaves like a shared server.
New objects can be created by a language constructor or a CORBA factory (factories are provided by the Life Cycle Service).

The implementation of the server is not regulated by CORBA, but its relationship to BOA is standardized through the BOA API. Each BOA implementation should provide the functionality for object management, which includes management of an Implementation Repository. BOA has to provide the mechanism for invoking methods and passing them parameters using skeletons. In a case of an error in execution of a request, BOA.set_exception can be used to inform the caller through an exception.

Implementation Repository

An Implementation Repository is used to store implementations of objects. A server should call the BOA.create method of the pseudo-object CORBA::BOA to describe the implementation of a new object. To bind a new object with an object reference that will be used to satisfy invocation requests BOA needs an interface name for the Interface Repository, an implementation name for the Implementation Repository and a unique reference data (ID). The names are supplied by the server, but the IDL compiler has provisions to generate global identifiers. BOA returns a reference to the registered object deposited in the Implementation Repository. The reference data ID might be used, for example, to identify an object in some kind of a persistent storage (managed by the server). If the object was already registered but is not running, then the server can ask for its ID by using BOA.get_id and retrieve the object from the storage. When the object is ready, the server calls the BOA.obj_is_ready method. When all of the objects are ready, then the server calls BOA.impl_is_ready. At this moment, the services provided by the servers objects (through their methods) can be accessed by clients through the remote invocation directed through BOA. An object that terminates its service can call BOA.deactivate_obj. The server informs the ORB that it is terminating by calling BOA.deactivate_impl.

Interface Definition Language (IDL)

As its name suggests, IDL is a language for specifying interfaces. With IDL, we specify interfaces to all objects that will connect to ORB. An interface defines the behavior of an object; i.e. its attributes (public variables), methods and their parameters, the exceptions that the object may raise and typed events associated with the object. The objects are described by their prototypes; i.e. classes. IDL is object-oriented, so it supports inheritance. Like C++, on which IDL is based, IDL allows for multiple inheritance; i.e. acquiring behaviors of several parent objects.

Like its C++ counterpart, the IDL compiler uses a precompiler to preprocess the source. C++-like precompiler directives can be included in the specifications. For example, the #include directive can be used for including other specification files. The description of the interface may also include special directives called pragmas; e.g., for generating unique global interface identifiers. Such identifiers can be used to reference services that are available on any accessible ORB supporting IIOP.

IDL is not a programming language. In fact, it is independent of any programming language and completely declarative; i.e. the implementation details are left for the application programmers. IDL provides bindings to several programming languages, so the interfaces can be easily incorporated in applications written in these languages. IDL not only provides access to the components, but also to all services that are available through an ORB.

The IDL syntax provides for declaration of the following elements:

The following is a structure of an IDL file:

module <module_name>
{
  <type declarations>;
  <constant declarations>;
  <exception declarations>;
  interface <interface_name> [: <inheritance> ]
  {
    <type declarations>;
    <constant declarations>;
    <exception declarations>;
    <operation_type> operation_name [ <parameters> ]
    [ raises exception ] [ context ];
    .
    .
    .
  }
  .
  .
  .
}

In the following example, we declare a module for several common household devices. Each of the devices has an owner, so they all inherit from the ownership interface. There are several abstract types of devices for entertainment, communication and Web browsing. The last two interfaces are for a TV and a telephone. In the declarations of both of them, we use multiple inheritance. A TV can be used for both entertainment and Web browsing. A telephone can also be used for Web browsing, but its primary function so far has been communication with other people. Both of them have an owner and they both can be sold. This behavior is inherited indirectly from the ownership interface.

module Devices
{
  interface ownership
  {
    attribute string owner;
    void sell(in string new_owner);
  };
  interface EntertainmentDevice : ownership
  {
    attribute string video_standard;
    exception NotAllowed {string explanation};
    void play(in string game_name)
    raises (NotAllowed);
  };
  interface CommDevice : ownership
  {
    attribute string medium;
    Exception NotResponding {string explanation};
    void connect(in string receiver)
    raises (NotResponding);
  };
  interface WebDevice : ownership
  {
    exception NotFound {string explanation};
    void browse(in string url)
    raises (NotFound);
  };
  interface CompDevice : ownership
  {
    exception TooBusy {string explanation};
    void Run(in string program)
    raises (TooBusy);
  };
  interface Computer
    : EntertainmentDevice, WebDevice, CompDevice, CommDevice
  {
    exception CompError {strig explanation};
    void compute(in string program)
    raises (CompError);
  };
  interface TV : EntertainmentDevice, WebDevice
  {
    exception NotOnAir {string explanation};
    void watch(in int channel)
    raises (NotOnAir);
  };
  interface Telephone : CommDevice, WebDevice
  {
    exception NotAtHome {string explanation};
    void talk(in string person_name)
    raises (NotAtHome);
  };
}
An IDL compiler is used to generate a number of elements that will be included in the client and server applications. We will look at them when we study an example of a Java application that uses CORBA.

Interface Repository

Interface Repository is a database that stores objects interfaces. The format of the storage is compatible with IDL. In fact, the IDL precompiler generates an Interface Repository from the IDL files.

The structure of the repository reflects the IDL definitions through a hierarchy of objects, which are instances of classes that correspond to the elements of the IDL. The root of the repository is the Repository object. The ORB.get_interface method returns a reference to the repository.

CORBA ORB provides a number of methods to access and manage the repository. Most of them are provided through several abstract classes: IRObject, Container and Contained. Every class in the repository is a subclass of one or more of these subclasses, so the methods can be invoked polymorphically on any object. Some methods are specific only to the Repository and InterfaceDef objects.

The following table presents a number of methods for accessing Interface Repositories. They can be used to generate invocation requests dynamically or to verify correctness of remote calls. If the reference to an object is known, then the ORB::Object.get_interface method can be used to obtain a reference to the object’s interface (an instance of InterfaceDef).
 
Repository.lookup_id To find an object in the repository by its Repository ID.
Container.lookup_name To obtain an object reference by name
Contained.describe To obtain a structure with an IDL description of the object.
Container.contents To obtain a list of contained objects. It is useful to navigate through a hierarchy of objects.
InterfaceDef. 
describe_interface
To retrieve a description of the interface to an object.
InterfaceDef.is_a To check whether the object is a subclass of the specified class (inherits interface).

Global Repository IDs

Global Repository IDs are used to avoid name conflicts in multiple repositories on interacting ORBs. CORBA defines two format for global identifiers: A global repository number can also be generated automatically by including special pragmas in the IDL specifications. The IDL identifier can be generated with the use of the prefix and version pragmas.
#pragma prefix DevicesCorp
#pragma version Computer 1.2
An arbitrary identifier can be assigned to an interface through the ID pragma. For example,
#pragma ID MyInterface "This_Better_Be_A_Valid_IDL_or_DCE_Id"

Static method invocation

The static mechanism of method invocation is very similar to an RPC call. The client needs to know the reference to the object providing the method. CORBA requires that all ORB implementations provide the same object mapping for any language. Therefore, both client and server handle objects in the language specific way. Any required transformation of the reference is handled transparently by the involved ORBs. The vendors have to use a special Interoperable Object Reference (IOR) in inter-ORB communication.

A reference to an object can be obtained from directories or invocations to other objects. Usually a binding utility can be used by clients. An object reference can be translated into a string using the ORB.object_to_string method of the ORB pseudo-object. This string can be stored or communicated to others, who can transform it back to a reference by calling ORB.string_to_object.

The identifier, the method and the input and output parameters are handled by a local stub. The stub uses the ORB core to communicate with the BOA on the node running the remote server. The BOA ensures that the called object is active, and then passes the control to the skeleton. The remote skeleton calls appropriate method on the server object, which performs the requested operation and returns the results to the skeleton. Analogously to the stub, the skeleton uses the core of ORB to send back the results to the caller through the client stub. Owing to the IOR and IIOP, the client does not care what ORB the server object is working with.

We will analyze the details while presenting a Java example in later sections.

Dynamic method invocation

The static method invocation is simple, and therefore relatively fast. It is explicit, so easy to debug. It provides robust type checking during the compilation, so errors are captured early in the development process. The price for all of this is its inflexibility. If a new service is provided after installing a client, then to use the service the client needs to be modified. This is very serious deficiency, because today’s computer networks are increasingly more dynamic. If we want our client to access services provided on the Internet, then it would be unreasonable to expect that all possibilities are predicted and encoded. Java is one possible solution, but if we want to use CORBA-compliant services we need to interface properly to the ORB through stubs or another mechanism. This other mechanism is the Dynamic Invocation Interface (DII). The DII makes use of the Interface Repository and several pseudo-objects to perform an might be a distributed repository, which is maintained by CORBA. The distribution of interfaces is not a concern of an application developer. The services provided by the BOA should take care of it.

The client has to discover who is it that needs to be called. That may come from the CORBA Naming or Trading Services. Alternatively, another application can send a string representation of the object, which can be transformed back into a reference by ORB.string_to_object. The Naming Service is useful when more details about the required server are available, while the Trading Service relies on the clients needs to find appropriate server. When a reference to the object is available, then the Interface Repository can be asked for the reference to the objects interface by calling get_interface on this object. As we know, this method is inherited from the Object pseudo-object of which every object is a subclass. The returned value is a handle of the interface object (an instance of InterfaceDef). It can be used to obtain the description of the interface. For example, InterfaceDef.lookp_name can be used to get a hanlde of a particular method and InterfaceDef.describe tp obtain an IDL description of the method’s interface. With this information, we can create a Named Value List (NVList) object that is used to pass parameters. This can be done by calling ORB.create_list and a series of calls to NVList.add_item, or ORB.create_operation_list can be used instead. A request can be constructed dynamically by calling the create_request on the object reference. This method is inherited from the Object pseudo-object. It returns a handle of a new instance of class Request. A shorter construction process for requests without parameters can be applied by calling the _request method on the object, which is inherited as well. With a request constructed, the server can be called in one of three ways. The first uses the Request.invoke method to invoke the remote method and obtain results in one shot. The caller is suspended waiting for the results. The Request.send_deferred, Request.poll_response and Request.get_response methods can be used to obtain a deferred response by the means of polling the server. If the client just wants to activate certain service, but is not expecting any results, then the last invocation method, Request.send_oneway, can be used.

As we have already seen, the interactions with the server object are controlled by the Object Adapter. It will ensure that the dynamically built request is fulfilled by the appropriate object. If the object is not active, then it will be activated as a process or a thread appropriately to the server’s mode.
 

Count example in VisiBroker CORBA

We will implement the same simple example that was used for demonstrating communication using plain Java sockets. First, we will analyze the static version. Then we will look at a version that uses DII.

Warning: The textbook uses VisiBroker CORBA. There are slight variations in uses of CORBAs from different users. For example, here we do not call BOA.create() (it is done in the skeleton when the constructor for a server object calls super(nameOfObject), which is a requirement).

Count interface in IDL

// Count.idl

module Counter
{
  interface Count
  { attribute long sum;
    long increment();
  };
};

Compiling IDL

prompt> idl2java -T Count.idl -no_comments

idl2java compiler creates a number of Java classes grouped in the Counter package. Two files are for the client side, and three groups are for the server.

Modules generated for server:

package Counter;
public interface Count extends CORBA.Object {
  public void sum(int sum) throws CORBA.SystemException;
  public int sum() throws CORBA.SystemException;
  public int increment() throws CORBA.SystemException;
} package Counter;
public class _example_Count extends Counter._sk_Count {
  public _example_Count(java.lang.String name) {
    super(name);
  }
  public _example_Count() {
    super();
  }
  public int increment() throws CORBA.SystemException {
    // implement operation...
  }
  public void sum(int sum) throws CORBA.SystemException {
    // implement attribute writer...
  }
  public int sum() throws CORBA.SystemException {
    // implement attribute reader...
  }
}

Modules generated for client

Server ‘s code

The following code has been created by extending Counter._example_Count.

// CountImpl.java: The Count Implementation
class CountImpl extends Counter._sk_Count implements Counter.Count
{
  private int sum;

  // Constructor
  CountImpl(String name)
  { super(name);
    System.out.println("Count Object Created");
    sum = 0;
  }
  // get sum
  public  int sum() throws CORBA.SystemException
  { return sum;
  }

  // set sum
  public  void sum(int val) throws CORBA.SystemException
  { sum = val;
  }

  // increment method
  public int increment() throws CORBA.SystemException
  { sum++;
    return sum;
  }
}

// CountServer.java: The Count Server main program

class CountServer
{ static public void main(String[] args)
  { try
    { // Initialize the ORB.
      CORBA.ORB orb = CORBA.ORB.init();

      // Initialize the BOA.
      CORBA.BOA boa = orb.BOA_init();

      // Create the Count object.
      CountImpl count = new CountImpl("My Count");

      // Export to the ORB newly created object.
      boa.obj_is_ready(count);

      // Ready to service requests.
      boa.impl_is_ready();
      }
      catch(CORBA.SystemException e)
      { System.err.println(e);
      }
   }
}

Client’s code

// CountClient.java  Static Client, VisiBroker for Java

class CountClient
{ public static void main(String args[])
  { try
    { // Initialize the ORB
      System.out.println("Initializing the ORB");
      CORBA.ORB orb = CORBA.ORB.init();

      // Bind to the Count Object
      System.out.println("Binding to Count Object");
      Counter.Count counter = Counter.Count_var.bind("My Count");

      // Set sum to initial value of 0
      System.out.println("Setting sum to 0");
      counter.sum((int)0);

      // Calculate Start time
      long startTime = System.currentTimeMillis();

      // Increment 1000 times
      System.out.println("Incrementing");
      for (int i = 0 ; i < 1000 ; i++ )
      { counter.increment();
      }

      // Calculate stop time; print out statistics
      long stopTime = System.currentTimeMillis();
      System.out.println("Avg Ping = "
                       + ((stopTime - startTime)/1000f) + " msecs");
      System.out.println("Sum = " + counter.sum());
    } catch(CORBA.SystemException e)
    { System.err.println("System Exception");
      System.err.println(e);
    }
  }
}

Running the application

The binding functionality is provided by VisiBroker’s osagent. It has to started prior to running the server and client.

> start osagent
> start java CountServer
> java CountClient

Applet client

We need two components for the client: Java code and HTML anchor for the applet.

// CountClientApplet.java  Applet Client, VisiBroker for Java

// NOTE: This code works with pre-JDK 1.1 browsers

import java.awt.*;

public class CountClientApplet extends java.applet.Applet
{
  private TextField countField, pingTimeField;
  private Button runCount;
  private Counter.Count counter;
  public void init()
  {
    // Create a 2 by 2 grid of widgets.
    setLayout(new GridLayout(2, 2, 10, 10));

    // Add the four widgets, initialize where necessary
    add(new Label("Count"));
    add(countField = new TextField());
    countField.setText("1000");
    add(runCount = new Button("Run"));
    add(pingTimeField = new TextField());
    pingTimeField.setEditable(false);

    try
    {
      // Initialize the ORB.
      showStatus("Initializing the ORB");
      CORBA.ORB orb = CORBA.ORB.init(this);

      // Bind to the Count Object
      showStatus("Binding to Count Object");
      counter = Counter.Count_var.bind("My Count");
    }
    catch(CORBA.SystemException e)
    {
      showStatus("Applet Exception" + e);
    }
  }

  public boolean action(Event ev, Object arg)
  {
    if(ev.target == runCount)
    {
      try
      {
        // Set Sum to initial value of 0
        showStatus("Setting Sum to 0");
        counter.sum((int)0);

        // get data from and set value of applet fields
        showStatus("Incrementing");
        int stopCount = Integer.parseInt(countField.getText());
        pingTimeField.setText(" ");

        // Calculate Start time
        long startTime = System.currentTimeMillis();

        // Increment stopCount times
        for (int i = 0 ; i < stopCount ; i++ )
        {
          counter.increment();
        }

        // Calculate stop time; show statistics
        long stopTime = System.currentTimeMillis();
        pingTimeField.setText("Avg Ping = "
                  + Float.toString((float)(stopTime- startTime)/stopCount)
                  + " msecs");
        showStatus("Sum = " + counter.sum());
      }
      catch(CORBA.SystemException e)
      {
        showStatus("System Exception" + e);
      }
      return true;
    }
    return false;
  }
}

HTML file

<h1>Count Client Applet</h1>
<hr>
<center>
<APPLET CODE=CountClientApplet.class WIDTH=300 HEIGHT=60
        CODEBASE=classes>
</APPLET>
</center>
<hr>

The client and the server should be installed on the Web server. Netscape Navigator supports IIOP, so after the applet is downloaded, it can communicate with the remote object.

Some extras might be required to address the security restrictions of applets.

Dynamic invocation

// CountClientDii.java  Dynamic Client, VisiBroker for Java

class CountClientDii
{ public static void main(String args[])
  {
    boolean loop_all = false;
    long startTime, stopTime;
    CORBA.Request request;

    try
    { // Initialize the ORB.
      System.out.println("Initializing the ORB");
      CORBA.ORB orb = CORBA.ORB.init();

      // Bind to the Count Object
      System.out.println("Binding to Count Object");
      Counter.Count counter = Counter.Count_var.bind("My Count");

      // Set Sum to initial value of 0
      System.out.println("Setting Sum to 0");
      counter.sum((int)0);

      if ((args.length != 0) &&
          (args[0].compareTo("loop_all") == 0))
         loop_all = true;

      if (loop_all)
      {
        System.out.println("Starting IR lookup + invoke test");
        // Calculate Start time
        startTime = System.currentTimeMillis();

        for (int i = 0 ; i < 1000 ; i++ )
        {
          request = buildRequest(counter);
          request.invoke();
        }
      }else
      {
        System.out.println("Starting invoke only test");
        request = buildRequest(counter);

        // Calculate Start time
        startTime = System.currentTimeMillis();

        // Increment 1000 times
        for (int i = 0 ; i < 1000 ; i++ )
        { request.invoke();
        }
      }
      // Calculate stop time; print out statistics
      stopTime = System.currentTimeMillis();
      System.out.println("Avg Ping = "
                         + ((stopTime - startTime)/1000f)
                         + " msecs");
      System.out.println("Sum = " + counter.sum());
    }
    catch(CORBA.SystemException e)
    { System.err.println("System Exception");
      System.err.println(e);
    }
  }

  public static CORBA.Request buildRequest(Counter.Count counter)
                              throws CORBA.SystemException
  {
    //get interface for Count object
    CORBA.InterfaceDef CountInterface = counter._get_interface();

    // describe interface for Count object
    CORBA._InterfaceDef.FullInterfaceDescription intDesc =
    CountInterface.describe_interface();

    if (intDesc.operations[0].name.compareTo("increment") == 0)
    { //create request object for dynamic increment
      CORBA.Request request = counter._request("increment");
      // initialize result value
      request.result().value().from_long(0);
      return request;
    } else
      System.out.println("Unknown method");
    return null;
  }
}

Running the application

prompt>start osagent
prompt>start irep myIR myIR.dat
prompt>idl2ir count.idl
prompt>start java CountServer
prompt>java CountClientDii