Chapter 37
JavaIDL: A Java Interface to CORBA

by Mark Wutka

The Common Object Request Broker Architecture (CORBA) is a tremendous vision of distributed objects interacting without regard to their location or operating environment. CORBA is still in its infancy, with some standards still in the definition stage, but the bulk of the CORBA infrastructure is defined. Many software vendors are still working on some of the features that have been defined.

What Is CORBA?

CORBA consists of several layers. The lowest layer is the Object Request Broker, or ORB. The ORB is essentially a remote method invocation facility. The ORB is language-neutral, meaning you can create objects in any language and use the ORB to invoke methods in those objects. You can also use any language to create clients that invoke remote methods through the orb. There is a catch to the "any language" idea. You need a language mapping defined between the implementation language and CORBA's Interface Definition Language (IDL).

IDL is a descriptive language--you cannot use it to write working programs. You can only describe remote methods and remote attributes in IDL. This restriction is similar to the restriction in Java that a Java interface contains only method declarations and constants.

When you go from IDL to your implementation language, you generate a stub and a skeleton in the implementation language. The stub is the interface between the client and the ORB, while the skeleton is the interface between the ORB and the object (or server). Figure 37.1 shows the relationship between the ORB, an object, and a client wishing to invoke a method on the object.

FIG. 37.1
CORBA clients use the ORB to invoke methods on a CORBA server.

While the ORB is drawn conceptually as a separate part of the architecture, it is often just part of the application. A basic ORB implementation might include the Naming service (discussed shortly) and a set of libraries to facilitate communication between clients and servers. Once a client locates a server, it communicates directly with that server, not going through any intermediate program. This permits efficient CORBA implementations.

The ORB is both the most visible portion of CORBA and the least exciting. CORBA's big benefit comes in all the services that it defines. Among the services defined in CORBA are

These services are a subset of the full range of services defined by CORBA. The Lifecycle and the Naming services crystallize Sun's visionary phrase "the network is the computer." These services allow you to instantiate new objects without knowing where the objects reside. You might be creating an object in your own program space, or you might be creating an object halfway around the world, and your program will never know it.

The Lifecycle service allows you to create, delete, copy, and move objects on a specific system. As an application programmer you would prefer not to know where an object resides. As a systems programmer you need the Lifecycle service to implement this location transparency for the application programmer. One of the hassles you frequently run into in remote procedure call systems is that the server you are calling must already be up and running before you can make the call. The Lifecycle service removes that hassle by allowing you to create an object, if you need to, before invoking a method on it.

The Naming service allows you to locate an object on the network by name. You want the total flexibility of being able to move objects around the network without having to change any code. The Naming service gives you that ability by associating an object with a name instead of a network address.

The Persistence service allows you to save objects somewhere and retrieve them later. This might be in a file, or it might be on an object database. The CORBA standard doesn't specify which. That is left up to the individual software vendors.

The Event service is a messaging system that allows more complex interaction than a simple message call. You could use the Event service to implement a network-based observer- observable model, for example. There are event suppliers, which send events, and event consumers, which receive them. A server or a client is either push or pull. A push server sends events out when it wants to (it pushes them out), while a push client has a push method and automatically receives events through this method. A pull server doesn't send out events until it is asked--you have to pull them out of the server. A pull client does not receive events until it asks for them. It might help to use the term "poll" in place of "pull." A pull server doesn't deliver events on its own, it gives them out when it is polled. A pull client goes out and polls for events.

The Transaction service is one of the most complex services in the CORBA architecture. It allows you to define operations across multiple objects as a single transaction. This kind of transaction is similar to a database transaction. It handles concurrency, locking, and even rollbacks in case of a failure. A transaction must comply with a core set of requirements that are abbreviated ACID:

The Transaction service usually relies on an external transaction processing (TP) system.

The Object Querying service allows you to locate objects based on something other than name. For instance, you could locate all ships registered in Liberia or all Krispy Kreme donut locations in Georgia. This service would usually be used when your objects are stored in an object database.

The Properties service allows objects to store information on other objects. A property is like a sticky-note. An object would write some information down on a sticky-note and slap it on another object. This has tremendous potential because it allows information to be associated with an object without the object having to know about it.

The beauty of the whole CORBA system is that all of these services are available through the ORB interface, so once your program can talk to the ORB, you have these services available. Of course, your ORB vendor may not implement all of these services yet.

Suns IDL to Java Mapping

In order to use Java in a CORBA system, you need a standard way to convert attributes and methods defined in IDL into Java attributes and methods. Sun has proposed a mapping and released a program to generate Java stubs and skeletons from an IDL definition.

Defining interfaces in IDL is similar to defining interfaces in Java since you are defining only the signatures (parameters and return values) of the methods and not the implementation of the methods.

IDL Modules

A module is the IDL equivalent of the Java package. It groups sets of interfaces together in their own namespace. Like Java packages, IDL modules can be nested. The following is an example IDL module definition (shown without any definitions, which will be discussed soon):

module MyModule {
     // insert your IDL definitions here, you must have at least
     // one definition for a valid IDL module

};

This module would be generated in Java as a package called MyModule:

package MyModule;

When you nest modules, the Java packages you generate are also nested. For example, consider the following nested module definition:

module foo {
     module bar {
          module baz {
// insert definitions here
          };
     };

};


TIP: Don't forget to put a semicolon after the closing brace of a module definition. Unlike Java, C, and C++, you are required to put a semicolon after the brace in IDL.


The Java package definition for interfaces within the baz module would be

package foo.bar.baz;

IDL Constants

As in Java, you can define constant values in IDL. The format of an IDL constant definition is

const type variable = value;

The type of a constant is limited to boolean, char, short unsigned short, long, unsigned long, float, double, and string.

Constants are mapped into Java in an unusual way. Each constant is defined as a class with a single static final public variable, called value, that holds the value of the constant. This is done because IDL allows you to define constants within a module, but Java requires that constants belong to a class.

Here is an example IDL constant definition:

module ConstExample {
     const long myConstant = 123;

};

This IDL definition would produce the following Java definition:

package ConstExample;
public final class myConstant {
     public static final int value = (int) (123L);

}

IDL Data Types

IDL has roughly the same set of primitive data types as Java except for a few exceptions:

Enumerated Types

Unlike Java, IDL allows you to create enumerated types that represent integer values. The JavaIDL system turns the enumerated type into a class with public static final values.

Here is an example IDL enumerated type:

module EnumModule {
     enum Medals { gold, silver, bronze };

};

This definition would produce the following Java class:

package EnumModule;
public class Medals {
    public static final int gold = 0,
                   silver = 1,
                   bronze = 2;
    public static final int narrow(int i) throws sunw.corba.EnumerationRangeException {
     if (gold <= i && i <= bronze) {
         return i;
     }
     throw new sunw.corba.EnumerationRangeException();
    }

}

Since you are also allowed to declare variables of an enumerated type, JavaIDL creates a holder class that is used in place of the data type. The holder class contains a single instance variable called value that holds the enumerated value. The holder for the Medals enumeration would look like:

package EnumModule;
public class MedalsHolder
{
    //     instance variable 
    public int value;
    //     constructors 
    public MedalsHolder() {
     this(0);
    }
    public MedalsHolder(int __arg) {
     value = EnumModule.Medals.narrow(__arg);
    }

}

You can create a MedalsHolder by passing an enumerated value to the constructor:

MedalsHolder medal = new MedalsHolder(Medals.silver);

The narrow method performs range checking on values and throws an exception if the argument is outside the bounds of the enumeration. It returns the value passed to it, so you can use it to perform passive bounds checking. For example,

int x = Medals.narrow(y);

will assign y to x only if y is in the range of enumerated values for Medals; otherwise, it will throw an exception.

Structures

An IDL struct is like a Java class without methods. In fact, JavaIDL converts an IDL struct into a Java class whose only methods are a null constructor and a constructor that takes all the structure's attributes.

Here is an example IDL struct definition:

module StructModule {
     struct Person {
          string name;
          long age;
     };

};

This definition would produce the following Java class declaration (with some JavaIDL-specific methods omitted):

package StructModule;
public final class Person {
    //     instance variables
    public String name;
    public int age;
    //     constructors
    public Person() { }
    public Person(String __name, int __age) {
     name = __name;
     age = __age;
    }

}

Like the enumerated type, a struct also produces a holder class that represents the structure. The holder class contains a single instance variable called value. Here is the holder for the Person structure:

package StructModule;
public final class PersonHolder
{
    //     instance variable 
    public StructModule.Person value;
    //     constructors 
    public PersonHolder() {
     this(null);
    }
    public PersonHolder(StructModule.Person __arg) {
     value = __arg;
    }

}

Unions

The union is another C construct that didn't survive the transition to Java. The IDL union actually works more like the variant record in Pascal, since it requires a "discriminator" value. An IDL union is essentially a group of attributes, only one of which can be active at a time. The discriminator indicates which attribute is in use at the current time. A short example should make this a little clearer. Here is an IDL union declaration:

module UnionModule {
     union MyUnion switch (char) {
          case `a':      string aValue;
          case `b':     long bValue;
          case `c':      boolean cValue;
          default:     string defValue;
     };

};

The character value in the switch, known as the discriminator, indicates which of the three variables in the union is active. If the discriminator is `a', the aValue variable is active. Since Java doesn't have unions, a union is turned into a class with accessor methods for the different variables and a variable for the discriminator. The class is fairly complex. Here is a subset of the definition for the MyUnion union:

package UnionModule;
public class MyUnion {
//     constructor
    public MyUnion() {
//     only has a null constructor 
    }
    //     discriminator accessor
    public char discriminator() throws sunw.corba.UnionDiscriminantException {
//     returns the value of the discriminator
    }
    //     branch constructors and get and set accessors
    public static MyUnion createaValue(String value) {
//     creates a MyUnion with a discriminator of `a'
    }
    public String getaValue() throws sunw.corba.UnionDiscriminantException {
//     returns the value of aValue (only if the discriminator is `a' right now)
    }
    public void setaValue(String value) {
//     sets the value of aValue and set the discriminator to `a'
    }
    public void setdefValue(char discriminator, String value) 
throws sunw.corba.UnionDiscriminantException {
//     Sets the value of defValue and sets the discriminator. Although every
//     variable has a method in this form, it is only useful when you have
//     a default value in the union.
    }

}

The holder structure should be a familiar theme to you by now. JavaIDL generates a holder structure for a union. The holder structure for MyUnion would be called MyUnionHolder and would contain a single instance variable called value.

Sequences and Arrays

IDL sequences and arrays both map very neatly to Java arrays. Sequences in IDL may be either unbounded (no maximum size) or bounded (a specific maximum size). IDL arrays are always of a fixed size. Since Java arrays have a fixed size but the size isn't known at compile-time, the JavaIDL system performs runtime checks on arrays to make sure they fit within the restrictions defined in the IDL module.

Here is a sample IDL definition containing an array, a bounded sequence, and an unbounded sequence:

module ArrayModule {
     struct SomeStructure {
          long longArray[15];
          sequence <boolean> unboundedBools;
          sequence <char, 15> boundedChars;
     };

};

The arrays would be defined in Java as

public int[] longArray;
public boolean[] unboundedBools;
public char[] boundedChars;

Exceptions

CORBA has the notion of exceptions. Unlike Java, however, exceptions are not just a type of object, they are separate entities. IDL exceptions cannot inherit from other exceptions. Other than that, they work like Java exceptions and may contain instance variables.

Here is an example IDL exception definition:


module ExceptionModule {
     exception YikesError {
          string info;
     };};

This definition would create the following Java file (with some JavaIDL-specific methods removed):

package ExceptionModule;
public class YikesError
     extends sunw.corba.UserException {
    //     instance variables
    public String info;
    //     constructors
    public YikesError() {
     super("IDL:ExceptionModule/YikesError:1.0");
    }
    public YikesError(String __info) {
     super("IDL:ExceptionModule/YikesError:1.0");
     info = __info;
    }

}

Interfaces

Interfaces are the most important part of IDL. An IDL interface contains a set of method definitions, just like a Java interface. Like Java interfaces, an IDL interface may inherit from other interfaces. Here is a sample IDL interface definition:

module InterfaceModule {
     interface MyInterface {
          void myMethod(in long param1);
     };

};

IDL classifies method parameters as being either in, out, or inout. An in parameter is identical to a Java parameter--it is a parameter passed by value. Even though the method may change the value of the variable, the changes are discarded when the method returns.

An out variable is an output-only variable. The method is expected to set the value of this variable, which is preserved when the method returns, but no value is passed in for the variable (it is uninitialized).

An inout variable is a combination of the two--you pass in a value to the method; if the method changes the value, the change is preserved when the method returns.

The fact that Java parameters are in-only poses a small challenge when mapping IDL to Java. Sun has come up with a reasonable approach, however. For any out or inout parameters, you pass in a holder class for that variable. The CORBA method can then set the value instance variable with the value that is supposed to be returned.

Attributes

IDL allows you to define variables within an interface. These translate into get and set methods for the attribute. An attribute may be specified as readonly which prevents the generation of a set method for the attribute. For example, if you defined an IDL attribute as


attribute long myAttribute;

Your Java interface would then contain the following methods:


int getmyAttribute() throws omg.corba.SystemException;
void setmyAttribute() throws omg.corba.SystemException;

Methods

You define methods in IDL like you declare methods in Java, with only a few variations. One of the most noticeable differences is that CORBA supports the notion of changeable parameters. In other words, you can pass an integer variable x to a CORBA method, and that method can change the value of x. When the method returns, x has the changed value. In a normal Java method, x would retain its original value.

In IDL, method parameters must be flagged as being in, out, or inout. An in parameter cannot be changed by the method, which is the way all Java methods work. An out parameter indicates a value that the method will set, but it ignores the value passed in. In other words, if parameter x is an out parameter, the CORBA method cannot read the value of x, it can only change it. An inout parameter can be read by the CORBA method and can also be changed by it.

Here is a sample method declaration using an in, an out, and an inout parameter:

long performCalculation(in float originalValue,
     inout float errorAmount, out float newValue);

Since Java doesn't support the notion of parameters being changed, the Java-IDL mapping uses special holder classes for out and inout parameters. The IDL compiler already generates holder classes for structures and unions. For base types like long or float, JavaIDL has built-in holders of the form TypenameHolder. For example, the holder class for the long type is called LongHolder. Each of these holder classes contains a public instance variable called value that contains the value of the parameter.

The other major difference between IDL and Java method declarations is in the way exceptions are declared. IDL uses the raises keyword instead of throws. In addition, the list of excep- tions are enclosed by parentheses. Here is a sample method declaration that throws several exceptions:

void execute() raises (ExecutionError, ProgramFailure);

Creating a Basic CORBA Server

The interface between the ORB and the implementation of a server is called a skeleton. A skeleton for an IDL interface receives information from the ORB, invokes the appropriate server method, and sends the results back to the ORB. You normally don't have to write the skeleton itself; you just supply the implementation of the remote methods.

Listing 37.1 shows an IDL definition of a simple banking interface. You will see how to create both a client and a server for this interface in JavaIDL.

Listing 37.1Source Code for Banking.idl


module banking {

     enum AccountType {
          CHECKING,
          SAVINGS
     };

     struct AccountInfo {
          string id;
          string password;
          AccountType which;
     };

     exception InvalidAccountException {
          AccountInfo account;
     };

     exception InsufficientFundsException {
     };

     interface Banking {

          long getBalance(in AccountInfo account)
               raises (InvalidAccountException);

          void withdraw(in AccountInfo account, in long amount)
               raises (InvalidAccountException,
                    InsufficientFundsException);

          void deposit(in AccountInfo account, in long amount)
               raises (InvalidAccountException);

          void transfer(in AccountInfo fromAccount,
               in AccountInfo toAccount, in long amount)
               raises (InvalidAccountException,
                    InsufficientFundsException);
     };

};

Compiling the IDL Definitions

Before you create any Java code for your CORBA program, you must first compile the IDL definitions into a set of Java classes. The idlgen program reads an IDL file and creates classes for creating a client and server for the various interfaces defined in the IDL file, as well as any exceptions, structures, unions, and other support classes.

To compile Banking.idl, the idlgen command would be

idlgen -fserver -fclient Banking.idl

idlgen allows you to use the C preprocessor to include other files, perform conditional compilation, and define symbols. If you do not have a C preprocessor, use the -fno-cpp option like this:

idlgen -fserver -fclient -fno-cpp Banking.idl

The -fserver and -fclient flags tell idlgen to create classes for creating a server and a client, respectively. You may not always need to create both, however. If you are creating a Java client that will use CORBA to invoke methods on an existing C++ CORBA server, you only need to generate the client portion. If you are creating a CORBA server in Java but the clients will be in another language, you only need to generate the server portion.

Using Classes Defined by IDL structs

When an IDL struct is turned into a Java class, it does not have custom hashCode and equals methods. This means that two instances of this class containing identical data will not be equal. If you want to add custom methods to these structs, you will have to create a separate class and define methods to convert from one class to the other.

One way to remedy this is to create a class that contains the same information as the IDL structure but also contains correct hashCode and equals methods, as well as a way to convert to and from the IDL-defined structure.

Listing 37.2 shows an Account class that contains the same information as the AccountInfo structure defined in Banking.idl.

Listing 37.2Source Code for Account.java

package banking;

// This class contains the information that defines
// a banking account.

public class Account extends Object
{
// Flags to indicate whether the account is savings or checking
     public String id;     // Account id, or account number
     public String password;     // password for ATM transactions
     public int which;     // is this checking or savings

     public Account()
     {
     }

     public Account(String id, String password, int which)
     {
          this.id = id;
          this.password = password;
          this.which = which;
     }

// Allow this object to be created from an AccountInfo instance

     public Account(AccountInfo acct)
     {
          this.id = acct.id;
          this.password = acct.password;
          this.which = acct.which;
     }

// Convert this object to an AccountInfo instance

     public AccountInfo toAccountInfo()
     {
          return new AccountInfo(id, password, which);
     }

     public String toString()
     {
          return "Account { "+id+","+password+","+which+" }";
     }

// Tests equality between accounts.
     public boolean equals(Object ob)
     {
          if (!(ob instanceof Account)) return false;
          Account other = (Account) ob;

          return id.equals(other.id) &&
               password.equals(other.password) &&
               (which == other.which);
     }

// Returns a hash code for this object

     public int hashCode()
     {
          return id.hashCode()+password.hashCode()+which;
     }

}

JavaIDL Skeletons

When you create a CORBA server, the IDL compiler generates a server skeleton. This skeleton receives the incoming requests and figures out which method to invoke. You only need to write the actual methods that the skeleton will call.

JavaIDL creates an Operations interface that contains Java versions of the methods defined in an IDL interface. It also creates a Servant interface, which extends the Operations interface. The skeleton class then invokes methods on the Servant interface. In other words, when you create the object that implements the remote methods, it must implement the Servant interface for your IDL definition.


NOTE: This technique of defining the remote methods in an interface that can be implemented by a separate object is known as a TIE interface. In the C++ world, and even on some early Java ORBS, the IDL compiler would generate a skeleton class that implemented the remote methods. To change the implementation of the methods, you would create a subclass of the skeleton class. The subclass technique is often called a Basic Object Adaptor, or BOA. The advantage of the TIE interface under Java is that a single object can implement multiple remote interfaces. You can't do this with a BOA object, because Java doesn't support multiple inheritance.


For example, your implementation for the Banking interface might be declared as:

public class BankingImpl implements BankingServant

Listing 37.3 shows the full BankingImpl object that implements the BankingServant interface. Notice that each remote method must be declared as throwing sunw.corba.Exception.

Listing 37.3Source Code for BankingImpl.java

package banking;

import java.util.*;

// This class implements a remote banking object. It sets up
// a set of dummy accounts and allows you to manipulate them
// through the Banking interface.
//
// Accounts are identified by the combination of the account id,
// the password and the account type. This is a quick and dirty
// way to work, and not the way a bank would normally do it, since
// the password is not part of the unique identifier of the account.

public class BankingImpl implements BankingServant
{
     public Hashtable accountTable;

// The constructor creates a table of dummy accounts.

     public BankingImpl()
     {
          accountTable = new Hashtable();

          accountTable.put(
               new Account("AA1234", "1017", AccountType.CHECKING),
               new Integer(50000));     // $500.00 balance

          accountTable.put(
               new Account("AA1234", "1017", AccountType.SAVINGS),
               new Integer(148756));     // $1487.56 balance

          accountTable.put(
               new Account("AB5678", "4456", AccountType.CHECKING),
               new Integer(7742));     // $77.32 balance

          accountTable.put(
               new Account("AB5678", "4456", AccountType.SAVINGS),
               new Integer(32201));     // $322.01 balance
     }

// getBalance returns the amount of money in the account (in cents).
// If the account is invalid, it throws an InvalidAccountException

     public int getBalance(AccountInfo accountInfo)
     throws sunw.corba.SystemException, InvalidAccountException
     {

// Fetch the account from the table
          Integer balance = (Integer) accountTable.get(
               new Account(accountInfo));

// If the account wasn't there, throw an exception
          if (balance == null) {
               throw new InvalidAccountException(accountInfo);
          }

// Return the account's balance
          return balance.intValue();
     }

// withdraw subtracts an amount from the account's balance. If
// the account is invalid, it throws InvalidAccountException.
// If the withdrawal amount exceeds the account balance, it
// throws InsufficientFundsException.

     public synchronized void withdraw(AccountInfo accountInfo, int amount)
     throws sunw.corba.SystemException, InvalidAccountException,
          InsufficientFundsException
     {

          Account account = new Account(accountInfo);

// Fetch the account
          Integer balance = (Integer) accountTable.get(account);

// If the account wasn't there, throw an exception
          if (balance == null) {
               throw new InvalidAccountException(accountInfo);
          }

// If we are trying to withdraw more than is in the account,
// throw an exception

          if (balance.intValue() < amount) {
               throw new InsufficientFundsException();
          }

// Put the new balance in the account

          accountTable.put(account, new Integer(balance.intValue() -
               amount));
     }

// Deposit adds an amount to an account. If the account is invalid
// it throws an InvalidAccountException

     public synchronized void deposit(AccountInfo accountInfo, int amount)
     throws sunw.corba.SystemException, InvalidAccountException
     {

          Account account = new Account(accountInfo);

// Fetch the account
          Integer balance = (Integer) accountTable.get(account);

// If the account wasn't there, throw an exception
          if (balance == null) {
               throw new InvalidAccountException(accountInfo);
          }

// Update the account with the new balance
          accountTable.put(account, new Integer(balance.intValue() +
               amount));
     }

// Transfer subtracts an amount from fromAccount and adds it to toAccount.
// If either account is invalid it throws InvalidAccountException.
// If there isn't enough money in fromAccount it throws
// InsufficientFundsException.

     public synchronized void transfer(AccountInfo fromAccountInfo,
          AccountInfo toAccountInfo, int amount)
     throws sunw.corba.SystemException, InvalidAccountException,
          InsufficientFundsException
     {
          Account fromAccount = new Account(fromAccountInfo);
          Account toAccount = new Account(toAccountInfo);

// Fetch the from account
          Integer fromBalance = (Integer) accountTable.get(fromAccount);

// If the from account doesn't exist, throw an exception
          if (fromBalance == null) {
               throw new InvalidAccountException(fromAccountInfo);
          }

// Fetch the to account
          Integer toBalance = (Integer) accountTable.get(toAccount);

// If the to account doesn't exist, throw an exception
          if (toBalance == null) {
               throw new InvalidAccountException(toAccountInfo);
          }

// Make sure the from account contains enough money, otherwise throw
// an InsufficientFundsException.

          if (fromBalance.intValue() < amount) {
               throw new InsufficientFundsException();
          }

          
// Subtract the amount from the fromAccount
          accountTable.put(fromAccount,
               new Integer(fromBalance.intValue() - amount));

// Add the amount to the toAccount
          accountTable.put(toAccount,
               new Integer(toBalance.intValue() + amount));
     }

}

Server Initialization

While JavaIDL is intended to be Sun's recommendation for mapping IDL into Java, it was released with a lightweight ORB called the Door ORB. This ORB provides just enough functionality to get clients and servers talking to each other but not much more.

Depending on the ORB, the initialization will vary, as will the activation of the objects. For the Door ORB distributed with JavaIDL, you initialize the ORB with the following line:

sunw.door.Orb.initialize(servicePort);

The servicePort parameter you pass to the ORB is the port number it should use when listening for incoming clients. It must be an integer value. Your clients must use this port number when connecting to your server.

After you initialize the ORB, you can instantiate your implementation object. For example,

BankingImpl impl = new BankingImpl();

Next, you create the skeleton, passing it the implementation object:

BankingRef server = BankingSkeleton.createRef(impl);

Finally, you activate the server by publishing the name of the object:

sunw.door.Orb.publish("Bank", server);

Listing 37.4 shows the complete JavaIDL startup program for the banking server.

Listing 37.4Source Code for BankingServer.java

package banking;

public class BankingServer
{

// Define the port that clients will use to connect to this server
     public static final int servicePort = 5150;

     public static void main(String[] args)
     {

// Initialize the orb
          sunw.door.Orb.initialize(servicePort);

          try {

               BankingImpl impl = new BankingImpl();
// Create the server
               BankingRef server =
                    BankingSkeleton.createRef(impl);

// Register the object with the naming service as "Bank"
               sunw.door.Orb.publish("Bank", server);

          } catch (Exception e) {
               System.out.println("Got exception: "+e);
               e.printStackTrace();
          }
     }

}

Creating CORBA Clients with JavaIDL

Since the IDL compiler creates a skeleton class on the server side that receives remote method invocation requests, you might expect that it creates some sort of skeleton on the client side that sends these requests. It does, but the client side class is referred to as a stub.

The stub class implements the Operations interface (the same Operations interface implemented by the Servant class). Whenever you invoke one of the stub's methods, the stub creates a request and sends it to the server.

There is an extra layer on top of the stub called a reference. This reference object, which is the name of the IDL interface followed by Ref, is the object you use to make calls to the server.

There are two simple steps in creating a CORBA client in JavaIDL:

  1. Create a reference to a stub using the createRef method in the particular stub.
  2. Use the sunw.corba.Orb.resolve method to create a connection between the stub and a CORBA server.

You would create a reference to a stub for the banking interface with the following line:

BankingRef bank = BankingStub.createRef();

Next, you must create a connection between the stub and a CORBA server by "resolving" it. Since JavaIDL is meant to be the standard Java interface for all ORBs, it requires an ORB-independent naming scheme. Sun decided on an URL-type naming scheme of the format:

idl:orb_name://orb_parameters

The early versions of JavaIDL shipped with an ORB called the Door ORB, which is a very lightweight ORB containing little more than a naming scheme. To access a CORBA object using the Door ORB, you must specify the host name and port number used by the CORBA server you are connecting to and the name of the object you are accessing. The format of this information is

hostname:port/object_name

If you wanted to access an object named Bank with the Door ORB, running on a server at port 5150 on the local host, you would resolve your stub this way:

sunw.corba.Orb.resolve(
"idl:sunw.door://localhost:5150/Bank",

bank);

Remember that the bank parameter is the BankingRef returned by the BankingStub.createRef method. Once the stub is resolved, you can invoke remote methods in the server using the stub. Listing 37.5 shows the full JavaIDL client for the banking interface. As you can see, once you have connected the stub to the server, you can invoke methods on the stub just like it was a local object.

Listing 37.5Source Code for BankingClient.java

import banking.*;

// This program tries out some of the methods in the BankingImpl
// remote object.

public class BankingClient
{

     public static void main(String args[])
     {

// Create an Account object for the account we are going to access.

          Account myAccount = new Account(
               "AA1234", "1017", AccountType.CHECKING);

          AccountInfo myAccountInfo = myAccount.toAccountInfo();
          try {

// Get a stub for the BankingImpl object

               BankingRef bank = BankingStub.createRef();
               sunw.corba.Orb.resolve(
                    "idl:sunw.door://localhost:5150/Bank",
                    bank);

// Check the initial balance
               System.out.println("My balance is: "+
                    bank.getBalance(myAccountInfo));

// Deposit some money
               bank.deposit(myAccountInfo, 50000);

// Check the balance again
               System.out.println("Deposited $500.00, balance is: "+
                    bank.getBalance(myAccountInfo));

// Withdraw some money
               bank.withdraw(myAccountInfo, 25000);

// Check the balance again
               System.out.println("Withdrew $250.00, balance is: "+
                    bank.getBalance(myAccountInfo));

               System.out.flush();
               System.exit(0);

          } catch (Exception e) {
               System.out.println("Got exception: "+e);
               e.printStackTrace();
          }
     }
}

Creating Callbacks in CORBA

Callbacks are a handy mechanism in distributed computing. You use them whenever your client wants to be notified of some event, but doesn't want to sit and poll the server to see if the event has occurred yet. In a regular Java program, you'd just create a callback interface and pass the server an object that implements the callback interface. When the event occurred, the server would invoke a method in the callback object.

As it turns out, callbacks are just that easy in CORBA. You define a callback interface in your IDL file and then create a method in the server's interface that takes the callback interface as a parameter. The following IDL file defines a server interface and a callback interface:

module callbackDemo
{
     interface callbackInterface {
          void doNotify(in string whatHappened);
     };

     interface serverInterface {
          void setCallback(in callbackInterface callMe);
     };

};

Under JavaIDL, the setCallback method would be defined as

void setCallback(callbackDemo.callbackInterfaceRef callMe)
    throws sunw.corba.SystemException;

Once you have the callbackDemo.callbackInterfaceRef object, you can invoke its whatHappened method at any time. At this point, the client and server are on a peer-to-peer level. They are each other's client and server.

Wrapping CORBA Around an Existing Object

When you create CORBA implementation objects you are tying that object to a CORBA implementation. While the Servant interface generated by the JavaIDL system goes a long way in separating your implementation from the specifics of the ORB, your implementation methods can throw the CORBA SystemException exception, tying your implementation to CORBA. This is not the ideal situation.

You can solve this problem, but it takes a little extra work up front. First, concentrate on implementing the object you want, without using CORBA, RMI, or any other remote interface mechanism. This will be the one copy you use across all your implementations. This object, or set of objects, can define it own types, exceptions, and interfaces.

Next, to make this object available remotely, define an IDL interface that is as close to the object's interface as you can get. There may be cases where they won't match exactly, but you can take care of that.

Once you generate the Java classes from the IDL definition, create an implementation that simply invokes methods on the real implementation object. This is essentially the same thing as a TIE interface, with one major exception--the implementation class has no knowledge of CORBA. You can even use this technique to provide multiple ways to access a remote object. Figure 37.2 shows a diagram of the various ways you might provide access to your implementation object.

FIG. 37.2
A single object can be accessed by many types of remote object systems.

While this may sound simple, it has some additional complexities you must address. If your implementation object defines its own exceptions, you must map those exceptions to CORBA exceptions. You must also map between Java objects and CORBA-defined objects. Once again, the banking interface provides a good starting point for illustrating the problems and solutions in separating the application from CORBA.

The original banking interface was defined with a hierarchy of exceptions, a generic BankingException, with InsufficientFundsException and InvalidAccountException as subclasses. This poses a problem in CORBA, since exceptions aren't inherited. You must define a BankingException exception in your IDL file, such as the following:

exception BankingException {};

In addition, since you probably want the banking application itself to be in the banking package, change the IDL module name to remotebanking.

The implementation for the Banking interface in the remotebanking module must perform two kinds of mapping. First, it must convert instances of the Account object to instances of the AccountInfo object. This may seem like a pain and, frankly, it is. But it's a necessary pain. If you start to intermingle the classes defined by CORBA with the real implementation of the application, you will end up having to carry the CORBA portions along with the application, even if you don't use CORBA.

Mapping to and from CORBA-Defined Types

You can define static methods to handle the conversion from the application data types to the CORBA-defined data types. For example, the banking application defines an Account object. The remotebanking module defines this object as AccountInfo. You can convert between the two with the following methods:

// Create a banking.Account from an AccountInfo object

public static banking.Account makeAccount(AccountInfo info)
{
        return new banking.Account(info.id, info.password,
                info.which);
}

// Create an AccountInfo object from a banking.Account object

public static AccountInfo makeAccountInfo(banking.Account account)
{
        return new AccountInfo(account.id, account.password,
                account.which);
}

Your remote implementation of the banking interface needs access to the real implementation, so the constructor for the RemoteBankingImpl object needs a reference to the banking.BankingImpl object:

protected banking.BankingImpl impl;
public RemoteBankingImpl(banking.BankingImpl impl)
{
        this.impl = impl;
}

Creating Remote Method Wrappers

Now, all your remote methods have to do is convert any incoming AccountInfo objects to banking.Account objects, catch any exceptions, and throw the proper remote exceptions. Here is the implementation of the remote withdraw method:

// call the withdraw function in the real implementation, catching
// any exceptions and throwing the equivalent CORBA exception

public synchronized void withdraw(AccountInfo accountInfo, int amount)
throws sunw.corba.SystemException, InvalidAccountException,
        InsufficientFundsException, BankingException
{

        try {

// Call the real withdraw method, converting the accountInfo object
// to a banking.Account object first

                impl.withdraw( makeAccount(accountInfo), amount);

        } catch (banking.InvalidAccountException excep) {

// The banking.InvalidAccountException contains an Account object.
// Convert it to an AccountInfo object when throwing the CORBA exception

                throw new InvalidAccountException(
                        makeAccountInfo(excep.account));

        } catch (banking.InsufficientFundsException nsf) {
                throw new InsufficientFundsException();
        } catch (banking.BankingException e) {
                throw new BankingException();
        }
}

While it would be nice if you could get the IDL-to-Java converter to generate this automatically, it has no way of knowing exactly how the real implementation looks.

Using CORBA in Applets

Although the full CORBA suite represents a huge amount of code, the requirements for a CORBA client are fairly small. All you really need for a client is the ORB itself. You can access the CORBA services from another location on the network. This allows you to have very lightweight CORBA clients. In other words, you can create applets that are CORBA clients.

The only real restrictions on applets using CORBA is that an applet can make network connections back only to the server it was loaded from. This means that all the CORBA services must be available on the Web server (or there must be some kind of proxy set up).

Since an applet cannot listen for incoming network connections, an applet cannot be a CORBA server in most cases. You might find an ORB that gets around this restriction by using connections made by the applet. Most Java ORBs available today have the ability to run CORBA servers on an applet for a callback object. For a callback, an applet might create a server object locally and then pass a reference for its server object to a CORBA server running on another machine. That CORBA server could then use the reference to invoke methods in the applet as a client.

Figure 37.3 illustrates how an applet might act as a CORBA server.

FIG. 37.3
An applet may act as a server by passing a reference to a local CORBA server.

Choosing Between CORBA and RMI

CORBA and RMI each have their advantages and disadvantages. RMI will be a standard part of Java on both the client and server side, making it a good, cheap tool. Since it is a Java-only system, it integrates cleanly with the rest of Java. RMI is only a nice remote procedure call system, however.

CORBA defines a robust, distributed environment, providing almost all the necessary features for distributed applications. Not all of these features have been implemented by most vendors, yet. Most CORBA clients are offered free, but you must pay for the server software. This is the typical pricing model for most Internet software nowadays. If you don't need all the neat features of CORBA and don't want to spend a lot of money, RMI might be the right thing for you.

Your company may feel that Java is not yet ready for "prime time." If this is the case but you believe that Java is the environment of the future, you should start working CORBA into your current development plans, if possible.

CORBA is a language-independent system. You can implement your applications in C++ today using many of the Java design concepts. Specifically, keep the application and the user interface separated and make the software as modular as possible. If you use CORBA between the components of your system, you can migrate to Java by slowly replacing the various components with CORBA-based Java software.

If you are a programmer trying to convince your skeptical management about the benefits of Java, use CORBA to make a distributed interface into one of your applications (hopefully you have a CORBA product for the language your application is written in). Next, write a Java applet that implements the user interface for your application using CORBA to talk to the real application. You have instantly ported part of your application to every platform that can run a Java-enabled browser. Hopefully, your applet will perform as well as the old native interface to the application.

This same technique will open up your existing CORBA applications to non-traditional devices like cellular phones and PDAs. If you aren't ready to support those devices yet, at least you now have a pathway.