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.
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.
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.
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:
Foo
defining the methods that
correspond to the OMG IDL operations; from the developer's point
of view, an object reference for a CORBA Foo
object
implements this interface.
FooHelper
that defines auxiliary
methods, notably narrow()
which is the CORBA
counterpart of Java casting.
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.
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 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 |
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.
To invoke a CORBA object, you need a reference for the object. There are two ways to get a reference for a CORBA object:
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.
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);
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.
This section introduces Java IDL support for servers of transient CORBA objects. One of the Examples is a transient object server.
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++.
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().
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);
Copyright © 1996, 1997 Sun Microsystems, Inc., 2550 Garcia Ave., Mtn. View, CA. 94043-1100 USA., All rights reserved.