CORBA for Java Programmers

Principal Elements of the CORBA Architecture

The CORBA 2.0 specification is a few hundred pages long; it is far more extensive than can be summarized here. This section describes the major architectural elements: those that make remote invocation of CORBA objects possible and are essential to understanding the context of the Java IDL product.

CORBA Application Roles

An object reference is a handle to a CORBA object implemented somewhere on the network. A CORBA client is a program that invokes on an object reference. A CORBA server implements the object that an object reference denotes. Client and server are roles that CORBA applications play during their lifetimes. An application can be a client and a server at the same time.

Although "pure" clients are common, it's often the case that an application that is predominantly a client also needs to serve an object. For example, suppose a CORBA stock-watcher object accepts registrations to notify clients when a price crosses a threshold. The client that wishes to be notified passes an alert object as a parameter to the stock-watcher registration operation. The stock-watcher invokes the alert object when the price passes the threshold. In this case, the registering client is the alert object's server; it implements the alert object.

ORBs and Invocations

For a client to invoke a CORBA object operation, both the client and the server (the object implementation) must use a CORBA software component called an ORB (object request broker). ORBs are the common denominators that bridge the differences in location, platform, and programming language that can separate a client and a server. ORBs can contact each other across the network, can create and interpret object references (CORBA object handles), and can marshal parameters into and out of the format used by IIOP. In addition to enabling client/server communication, ORBs provide other services, but they are not described here.

There are two ways to invoke an operation on a CORBA object.

  1. Static invocation, which is used most often and most resembles conventional Java programming, is type-checked by the compiler. But it can only be used for objects whose interface definitions existed when the invoking client was compiled.
  2. Dynamic invocation is more flexible because clients can invoke objects they discover at run-time. However, compile-time type-checking is impossible with dynamic invocation. Although it's possible to type-check parameters at run-time, doing so requires a CORBA Interface Repository, which the current release of Java IDL does not provide.

Static Invocation

To statically invoke an operation on a CORBA object, a Java client needs two things:

Of these, the client developer is only interested in the object reference; the stub class is generated automatically and its instances are created and invoked automatically.

OMG IDL is the language in which CORBA object interfaces are defined. For each OMG IDL module the idltojava compiler generates a Java package. For each interface Foo defined in an OMG IDL module, the generated package contains the following items of interest to the client programmer:

Here's a simple example from the point of view of a client.

OMG IDL

Generated Java

module Example {
   interface Counter {
      boolean increment (in long arg);
   };
};
package Example;
public interface Counter 
   extends org.omg.CORBA.Object {
   public boolean increment (int arg);
}
 
public class CounterHelper {
   public static Counter narrow
      (org.omg.CORBA.Object obj);
   // ... 
} 
 
public final class CounterHolder {
   // ... 
}

The following Java fragment shows the essentials of declaring and invoking an instance of this CORBA object:

import Example.*  // get the interface and helper 
 
Counter anInstance = // acquire a reference
 
boolean aResult; 
int anArg = 10;
 
aResult = anInstance.increment(anArg);

A stub adapts a client to its underlying ORB for a particular CORBA object type. The ORB knows how to marshal an argument, but not which arguments need marshaling for a particular method. The stub, being type-specific, knows which arguments need marshaling for each method. When a client invokes a stub method, the stub tells the client's ORB what to marshal, and then tells the ORB to invoke the CORBA object identified by the object reference. The client's ORB uses the object reference to determine which remote ORB should receive the invocation, passes the marshaled parameters over the network to that ORB, and waits for the result. (For those familiar with remote procedure calls, CORBA object invocation is similar.)

A skeleton is an object server's analog of the client's stub. Skeletons are also generated by IDL compilers. When an ORB receives an invocation for one of its objects, it inspects the object reference to learn the identity of the object's server. The ORB then invokes the skeleton, which tells the ORB how to unmarshal the parameters, and finally invokes the server implementation of the operation requested by the client. The server method returns a result to the skeleton which arranges marshaling with the server ORB, which returns the result to the client ORB, which returns it to the stub, which returns it to the client. Recall that to a client, the stub and skeleton are invisible; to the client the object reference is the CORBA object.

To summarize, IIOP ORBs communicate CORBA object invocations across a network in a form that they all understand. All ORB operations are independent of the type of object being invoked, and the language in which the client and server are written. It's the job of a stub and a skeleton to match clients and servers to their ORBs for a particular type of CORBA object.

Dynamic Invocation

CORBA dynamic invocation uses an object called a request to hold everything pertinent to an invocation: the object reference, the name of the operation, its parameters, and space for the result. The client builds a request object describing an operation, then calls the request's invoke method, which dispatches the request just as a stub would. When the invoke method returns, the result is available in the request object.

The key to dynamic invocation is the ability of requests to hold self-describing data. This facility enables a request object to represent any invocation of any operation, regardless of its parameters are. Each self-describing data element has a special type known in OMG IDL as Any. An Any consists of a typecode (whose values are defined by OMG IDL) and a value; the typecode specifies the type of the value.

The following example shows the essentials of dynamic invocation. The CORBA object to be invoked has the following OMG IDL interface definition:

   interface Counter {
       long increment(in long inc);
    }; 
 

A Java application that creates and populates a request object with the data needed to invoke the CORBA object's increment operation is shown below.

import org.omg.CORBA.*;

public class CounterClient {

  public static void main(String[] args) 
    {
      int ctrInValue = 10;
      int ctrOutValue;

      // Get a hold of the object reference to the counter object.
      // This would typically be done by looking up a name service or
      // getting it as a result from some other operation.

      org.omg.CORBA.Object counter = _somehow_get_hold_of_counter();

      // Initialize the ORB.
      ORB _orb = ORB.init();

      // Set up the argument (as an NVList)
      NVList _arguments = _orb.create_list(1);
      Any _input = _orb.create_any();
      _input.insert_long(ctrInValue);
      _arguments.add_value("inc",		// name
			   _input, 		// value
			   ARG_IN.value);	// flags

      // Set up the result container (as a NamedValue)
      Any _result = _orb.create_any();				// create any
      _result.type(_orb.get_primitive_tc(TCKind.tk_long));	// set type
      NamedValue _resultNV = _orb.create_named_value(null, 	// name
						     _result, 	// value
						     0);	// flags

      // Create the request
      Request _request = counter._create_request(null,		// context
						"increment",	// operation
						_arguments, 	// arguments
						_resultNV);	// result
      
      // Invoke the operation
      _request.invoke();

      // Get a hold of the result
      ctrOutValue = _result.extract_long();
      System.out.println("Value returned from counter = " +  ctrOutValue);
    }
}

OMG IDL and Java Languages

OMG IDL is the language in which the interface of a CORBA object type is defined. Java is the language that Java IDL developers use to invoke and implement CORBA objects. The correspondence between the two languages is called the mapping, and is defined in Java IDL Language Mapping, which has been submitted to the Object Management Group. This section describes just the main points of the IDL-Java mapping. Note that OMG IDL is limited to describing interface syntax, as is the Java interface construct; semantics (what operations/methods mean) must be expressed in another language, such as English.

The following table lists the main elements of IDL and their correspondents in Java.

IDL Construct

Java Construct

module

package

interface

interface, helper class, holder class

constant

public static final

boolean

boolean

char, wchar

char

octet

byte

string, wstring

java.lang.String

short, unsigned short

short

long, unsigned long

int

long long, unsigned long long

long

float

float

double

double

enum, struct, union

class

sequence, array

array

exception

class

attribute

method


Note: When a CORBA operation takes a type that corresponds to a Java object (a string, for example), it is illegal to pass a Java null as the parameter value. Instead, pass an empty version of the designated object type (e.g., an empty String or array). A Java null only can be passed as a parameter whose type is a CORBA object reference, in which case the null is interpreted as a nil CORBA object reference.


CORBA and Java Object Models

At the level of invoking an operation/method, a CORBA object looks and behaves much like a Java object. The IDL-Java mapping and the stubs mask many of the inherent differences. However, there are some fundamental differences that the IDL-Java mapping and stubs cannot hide. These differences derive mainly from the fact that CORBA objects are potentially shared and distributed across a network, whereas Java objects are private to an address space.

Obtaining Object References

To invoke a CORBA object, you need a reference for the object. There are two ways to get a reference for a CORBA object:

  1. from another object, such as a factory or a name service
  2. from a string that was specially created from an object reference

It's common to encounter CORBA objects whose operations return references to other objects. A factory object is one example. Suppose there is a CORBA object type IntStackFactory that provides a create() operation as shown in the following OMG IDL:

module IntegerStack {
interface IntStack {
// push, pop, etc.
}
interface IntStackFactory {
IntStack create ();
}

The following Java fragment directs a stack factory to return a reference to a new CORBA stack object.

IntStack stack1 = anIntStackFactory.create();

Widely used CORBA objects, such as factories, are often registered by well-known names in CORBA-defined objects called naming contexts. Naming contexts are directory-like objects that contain sets of name-object bindings. Pass a registered object's name to a context's resolve() operation, and the context returns a reference to the object.

You can create a string from an object reference; the result is called stringified object reference. You can also do the reverse, create an object reference from a stringified reference. For example:

String stringifiedRef = ORB.object_to_string(obj);
org.omg.CORBA.Object obj = 
   ORB.string_to_object(stringifiedRef); 

Obtaining an orb object and an initial naming context are described in Initialization. Note that an object reference created from a string has the type org.omg.CORBA.Object; it must be narrowed to use it as its real type.

Narrowing

OMG IDL supports inheritance; the top of the hierarchy is Object in OMG IDL, org.omg.CORBA.Object in Java. (Note that both CORBA and Java define Object, but they are the tops of different type hierarchies.) Some operations, notably name lookup and unstringifying, return org.omg.CORBA.Objects, which you narrow (using a helper class generated by idltojava) to the derived type you want the object to be. CORBA objects must be explicitly narrowed because the Java runtime cannot always know the exact type of a CORBA object.

For example, if an application knows the CORBA object obj obtained above by unstringifying is in fact an IntStack, it can narrow it to one as follows:

IntStack aStack = IntStackHelper.narrrow(obj);

Creation and Deletion

To create a Java object you use the new() operator; to delete a Java object you do nothing; the garbage collector reclaims it for you. CORBA clients never create CORBA objects, only servers do. As a client, to create a CORBA object, you invoke another object which is the factory for the type of object you want created; the factory returns an object reference for you to use to invoke the new object. (Factory is a conventional term for an object that creates some kind of object, but lots of objects create and return other objects as results.) There's no reserved "create" operation on a factory; you need to consult the factory's documentation to learn which operation to invoke. Typically the factory is implemented by the same server that implements the object type.

Neither is there any standard way to delete a CORBA object. Because a CORBA object may be shared by many clients around a network, it is hard to know when an object has, in Java terms, become garbage. In general, only the object server is in a position to know, and then only if the server developer provides a "destroy" operation for the object which clients invoke when, from their point of view, an object can be destroyed. Alternatively, an object may be intended to persist forever, and therefore provide no "destroy" operation. In sum, it's up to you to learn how to use each CORBA object type.

Servers for Transient CORBA Objects

This section introduces Java IDL support for servers of transient CORBA objects. One of the Examples is a transient object server.

Transient and Persistent Objects

A transient CORBA object has the same lifetime as the execution of the server process that creates it. When a server terminates, its transient objects disappear with it. Transient objects are often used to effect asynchronous communication between applications and objects. For example, suppose a timing service will let a client know when an interval expires; it could do so as follows:

The arrangement, in other words, is much like a callback in procedural programming.

By contrast, a persistent object lives until it is explictly destroyed. If a client has a reference to a persistent CORBA object, that reference can be used even if the object's server is not running--an ORB will start the server when it (the ORB) receives an invocation on the object.

At present, Java IDL supports only transient object servers. Although persistent object servers can't yet be written in Java, Java applets and applications can be clients of persistent CORBA objects whose servers are written in other languages, such as C++.

Servant and Servant Base Classes

To implement a server for transient CORBA objects of some type, you write a Java class called a servant, which inherits from a class called the servant base; servant base classes are optionally generated by the idltojava compiler. An instance of a transient CORBA object is implemented by an instance of its servant class.

The servant base class is the CORBA type-specific interface between the ORB and the servant code for the type. It unmarshals incoming parameters, invokes servant methods, marshals results, and directs the ORB to return results to the client ORB.

A servant class extends its servant base with a method for each operation in the IDL interface definition it implements. (OMG IDL attributes require one method if they are read-only, two if they are writeable.) For example, consider the following OMG IDL:

module Notifiers {
   interface SimpleNotifier {
      void alert (in long alert_id);
   }
}

A servant for this type might look as follows:

import SimpleNotifierStubs.*
 
class SimpleNotifierServant extends _SimpleNotifierImplBase {
   
   void alert (int alert_id) {
      // do something about the alert ...
      return;
   }
}

Another client class could create instances of SimpleNotifierServant as follows:

  SimpleNotifierServant SimpleNotifierRef = new SimpleNotifierServant(); 
  orb.connect(SimpleNotifierRef); 

The orb.connect() call registers the object and the servant with the ORB so that the ORB will invoke the servant when the CORBA object is invoked. When the server terminates, the ORB destroys its records for that server's CORBA objects so that subsequent invocations will result in an OBJECT_NOT_EXIST exception. A server can alternatively annul its CORBA objects before terminating by calling orb.disconnect().

Using Servant Objects

A transient CORBA object is implemented by a Java servant object. A servant object can be passed as a parameter to any method that needs a CORBA object reference of that type. For example, suppose a Java program wants to learn when the price of Sun Microsystems stock rises above 150. The program has obtained a reference to a CORBA stock-watcher object which provides the following operation (shown in IDL and Java):

// IDL
interface StockWatcher {
void set_threshold(
   in string stock_symbol,
   in short stock_price,
   in Notifiers::SimpleNotifier notifier_obj);
}
 
// Java
void set_threshold(
   String stock_symbol, 
   short stock_price, 
   Notifiers.SimpleNotifier notifier_obj)
  { // set the threshold ... 
  }
 

To call set_threshold(), you need to create a SimpleNotifier CORBA object. Assuming you've imported a servant class called SimpleNotifierImpl, here's how to do it:

SimpleNotifier SUNWnotifier = new SimpleNotifierImpl(); 
int stockPrice = 150;
orb.connect();
StockWatcher aStockWatcher = // code to get a stock-watcher reference; 
 
aStockWatcher.set_threshold ("SUNW", stockPrice, SUNWnotifier);
 

 


Overview | Guide | API | idltojava | Mapping | Release Notes


Copyright © 1996, 1997 Sun Microsystems, Inc., 2550 Garcia Ave., Mtn. View, CA. 94043-1100 USA., All rights reserved.