If you are just tuning in, we're in the midst of a four part series on the enterprise CORBA features included in J2SE (Java 2 Platform, Standard Edition) 1.4. Part 1 provided a whirlwind tour of those new features. That article ended with a quick Hello World example to set the stage for Part 2, which detailed the Portable Object Adapter (POA), introduced in CORBA 2.2 to replace the Basic Object Adapter (BOA). I also delved into the mechanics behind Part 1's Hello World example. In this article, I show how to apply the knowledge you've gained about the POA to create an enterprise-level application. In the process, we look at how the POA enables us to create high performance, and highly scalable and available applications.

Read the whole series, "J2SE 1.4 Breathes New Life into the CORBA Community:"

All ORBs (object request brokers) have a default POA called the RootPOA. You obtain a reference to the POA just as you would a reference to an ORB service such as the naming service, that is, via the resolve_initial_reference() method on the ORB instance. As you recall, each POA has an associated set of policies that govern its various characteristics, such as object reference lifetime, object ID uniqueness, and servant management. The POA specification (part of the CORBA specification) specifies a set of policies that all RootPOAs must have. Most large-scale projects typically require POAs with policies that differ from the RootPOA's, which mandates new POA creation. To create a new POA, you need a reference to an existing POA, hence, you will always need to resolve a reference to the root POA, regardless of whether you will create object references with it. And finally, POAs observe a tree relationship similar to XML elements in the DOM (Document Object Model), with the RootPOA as the tree's root element.

An example: The CORBA bank

To put into perspective the various aspects of using a POA to create a more complex application (and to avoid boring you to death with theory), I will discuss a bank application. Using this application, a bank employee must:

  1. Create a new account
  2. Close an existing account
  3. Perform basic operations on existing accounts, such as deposits, withdrawals, and balance checks

Define the IDL

Let's start by defining what our bank example's programming/implementation language-independent IDL (interface definition language) will look like. The IDL will consist of the following elements:

  1. An enumeration that defines the different account types
  2. A structure that defines the search/creation criteria for an account
  3. Exceptions for different situations such as invalid account numbers and invalid account operations
  4. The Account interface definition that contains methods to allow deposits and withdrawals, and an attribute to allow retrieval of an account balance
  5. The Bank interface definition that contains methods to open, close, and find accounts

The complete IDL definition is shown below:

module bank {
  enum AccountType { NIL, Checking, Saving };
  struct AccountInformation
  {
    string accNum;
    AccountType accType;
  };
  exception NoSuchAccountException
  {
  };
  exception UnknownException
  {
    string reason;
  };
  exception InvalidOperationException
  {
    string reason;
  };
  interface Account
  {
    readonly attribute float balance;
    readonly attribute string accountNumber;
    readonly attribute AccountType accountType;
    void deposit(in float amt) raises(InvalidOperationException, UnknownException);
    void withdraw(in float amt) raises(InvalidOperationException, UnknownException);
  };
  typedef sequence<Account> Accounts;
  interface Bank {
    Account openAccount(in AccountInformation accInfo) raises(UnknownException);
    void closeAccount(in string accNum) raises(UnknownException);
    Accounts findAccounts(in AccountInformation accInfo) raises(UnknownException);
  };
};

Create the database

We will store the account information in a database. I use SQL Server 2000 on my Windows 2000 machine. You can use any database of your choice. Here is the SQL script that I run to create the Account table in a database called Bank (consult your database vendor's documentation for instructions on creating a new database instance or add this table to an existing instance):

CREATE TABLE Account(
             AccountNumber CHAR(15) NOT NULL PRIMARY KEY,
             AccountType   CHAR(1)  NOT NULL,
             Balance       FLOAT    NOT NULL
)

Implement the Bank object

Now implement the servant that implements the Bank interface. As you learned in Part 1, the idlj compiler, used to compile the IDL, generates a class that you extend to create your servant implementation. This compiler-generated class mainly contains marshalling code. The bank servant will extend the BankPOA class, which is (you guessed it) generated by idlj. The bank servant implementation is fairly straightforward with the exception of some noteworthy points:

  1. All the servant's methods use the static methods on the DBUtils class to access data from the database. For example, to create a new account record in the database, the openAccount() method calls the createAccount() method on DBUtils. DBUtils is an implementation of the Data Access Object pattern. To fulfill the data access function, you must configure DBUtils. Do that by placing the bank.properties file (available for download from Resources) in the classpath along with the DBUtils.class file—they should be in the same directory (or jarred from the same directory). You may need to change bank.properties's contents to match your database access parameters.
  2. All account objects are created using a special POA called AccountPOA (more about that POA later). You obtain a reference to AccountPOA by calling the find_POA() method on the root POA as shown below:

      POA accountPOA = this._poa().the_parent().find_POA("AccountPOA",true);
    
  3. References, not actual objects, return from both the openAccount() and findAccounts() methods to speed request processing and because we do not know if the client really wants to call methods on the account object. We defer actual object creation to client method invocation. You create an account reference with the create_reference_with_id() method on the AccountPOA and use the account number as the reference's object ID. For example, look at the code fragment from the openAccount() method, which creates and returns a reference to a new account object:

      Account theAccount = 
        AccountHelper.narrow(accountPOA.create_reference_with_id(accNum.getBytes(),
          "IDL:bank/Account:1.0"));
        return theAccount;
    
  4. When a call to the closeAccount() method closes an account, the account information is removed from the database, a reference to the AccountPOA is obtained, and the CORBA object is deactivated by calling the deactivate_object() method on the AccountPOA. Calling this method causes two things to happen:

    1. The entry corresponding to this servant and object ID drops from the active object map
    2. The etherialize() method is called on the servant activator (discussed in the following section) if one has been registered with the AccountPOA

The complete bank servant implementation is shown below for reference:

package bank;
import org.omg.CORBA.*;
import org.omg.PortableServer.*;
import java.util.List;
import java.util.Iterator;
public class BankImpl extends bank.BankPOA
{
  public bank.Account openAccount (bank.AccountInformation accInfo) throws 
           bank.UnknownException
  {
    try
    {
      DBUtils.createAccount(accInfo);
      POA accountPOA = this._poa().find_POA("AccountPOA",true);
      String accNum = accInfo.accNum;
      Account theAccount = AccountHelper.narrow(
        accountPOA.create_reference_with_id(accNum.getBytes(),
          "IDL:bank/Account:1.0"));
        return theAccount;
      }
      catch(Exception e)
      {
        throw new UnknownException(e.getMessage());
      }
  }
  public void closeAccount (String accNum) throws bank.UnknownException
  {
    try
    {
      DBUtils.removeAccount(accNum);
      POA accountPOA = this._poa().find_POA("AccountPOA",true);
      accountPOA.deactivate_object(accNum.getBytes());
    }
    catch(org.omg.PortableServer.POAPackage.ObjectNotActive e)
    {
    }
    catch(Exception e)
    {
      throw new UnknownException(e.getMessage());
    }
  }
  public bank.Account[] findAccounts (bank.AccountInformation accInfo) throws bank.UnknownException
  {
    try
    {
      // Find the Accounts and return references
      List accountNumbers = DBUtils.findAccounts(accInfo);
      POA accountPOA = this._poa().find_POA("AccountPOA",true);
      Account[] accounts = new Account[accountNumbers.size()];
      Iterator it = accountNumbers.iterator();
      int i=0;
      while(it.hasNext())
      {
        accounts[i] = AccountHelper.narrow(
          accountPOA.create_reference_with_id(((String)it.next()).getBytes(),
          "IDL:bank/Account:1.0"));
        i++;
      }
      return accounts;
    }
    catch(Exception e)
    {
      throw new UnknownException(e.getMessage());
    }
  }
}

Implement the Account object

As I mentioned above, the AccountPOA creates and manages account objects (and references). I will discuss three different ways to configure the AccountPOA and the resulting account servants. I list the three approaches below:

  1. Use a servant activator
  2. Use a servant locator
  3. Use a default servant

Obviously, depending on how creative you are and how much time you have, you can create a solution using a combination of these three methods, but I will not discuss that here.

The above three solutions correspond to the USE_SERVANT_MANAGER and USE_DEFAULT_SERVANT values of the request processing policy values. The third (and default) value, USE_ACTIVE_OBJECT_MAP_ONLY, does not really work here since we would have to create all the account objects during startup—not a very scalable solution when you consider that a typical bank could have several thousand accounts. Why create an account object (and thus consume computing resources) that might not be accessed more than once a month? I used this same line of thinking in the bank servant implementation when I decided to return object references rather than object instances in the openAccount() and findAccount() methods. When you return an object reference, the POA does not add an entry to the active object map, even if the POA has the RETAIN policy. The POA adds such an entry only when a servant has instantiated for that object reference.

Use a servant activator

When the POA has the RETAIN policy, it uses servant managers called servant activators. Remember from Part 2 that the RETAIN policy implies that the POA will maintain the association between the servant instance and an activated object in the active object map. When the POA receives a request, it extracts the object ID from the request and checks the active object map to see if a servant already exists that can service that object ID's request. If it cannot find a servant, then the POA invokes the incarnate() method on the servant activator (if it has been registered), passing in the object ID as a parameter. The POA expects this method to return a valid servant instance; it then forwards the request to the valid servant instance and puts the instance in the active object map. When the POA is shutting down or when a specific object reference is being deactivated, the etherealize() method on the servant activator will be called (in the case of the POA shutting down, the etherealize() method will be called many times; once for each object reference). Any object-specific cleanup can be handled here.

For multithreaded systems, the POA makes several guarantees to prevent race situations from occurring. For example, at any given instant, either the incarnate() or etherialize() method (but not both) will execute on a given servant activator. Additionally, only one thread can execute inside a servant activator at any given time. And finally, if a servant is being etherialized, all requests for that servant will queue up until the etherialize() method completes.

Here is an example servant activator implementation that registers with the AccountPOA:

Page 2 of 3
package bank;
import org.omg.CORBA.*;
import org.omg.PortableServer.*;
public class AccountServerActivatorImpl extends org.omg.PortableServer.ServantActivatorPOA
{
  public Servant incarnate(byte[] oid, POA adapter) throws ForwardRequest
  {
    // First check the DB to make sure account exists.
    // Then create an accountimpl and return.
    System.out.println("(AccountServerActivatorImpl) Returning an Account Object for account number " 
                         + new String(oid));
    try
    {
      DBUtils.getBalance(new String(oid));
    }
    catch(Exception e)
    {
      System.out.println("(AccountServerActivatorImpl) Oops... Invalid account number " 
                         + new String(oid));
      throw new org.omg.CORBA.OBJECT_NOT_EXIST();
    }
    return new AccountImpl(new String(oid));
  }
  public void etherealize(byte[] oid, POA adapter, Servant serv,
          boolean cleanup_in_progress, boolean remaining_activations)
  {
  }
}

The etherealize() method does nothing since no cleanup is necessary. Remember the closeAccount() method in the bank servant: it calls the static removeAccount() method on DBUtils. Could I have put the removeAccount() method call in the etherialize() method? That way, when the deactivate_object() call eventually leads to the etherealize() call, the account information would drop from the database. Assuming that I could live with the latency in the account removal, that approach would be perfect, right? No, not really; because object deactivation is not the only time etherealize() is called. etherealize() will also be called when AccountPOA is shutting down, and we definitely don't want to delete all the accounts when AccountPOA shuts down!

In addition to the etherealize() method, all servant activators must implement an incarnate() method. In our case, incarnate() checks to see if the account exists in the database, and, if the check passes, it creates an account servant instance (shown below) and returns it to the POA. As mentioned before, the POA will add this reference to the active object map and forward the client request to it.

// The account servant implementation
package bank;
public class AccountImpl extends bank.AccountPOA
{
  private String _accNum;
  public AccountImpl(String accNum)
  {
    this._accNum = accNum;
  }
  public float balance()
  {
    try
    {
      return DBUtils.getBalance(_accNum);
    }
    catch(Exception e)
    {
      return (float)-1.0;
    }
  }
  public String accountNumber()
  {
    return _accNum;
  }
  public bank.AccountType accountType ()
  {
    try
    {
      return DBUtils.getAccountType(_accNum);
    }
    catch(Exception e)
    {
      return AccountType.NIL;
    }
  }
  public void deposit (float amt) throws bank.InvalidOperationException, bank.UnknownException
  {
    if(amt <= (float)0.0)
     throw new InvalidOperationException("Invalid deposit amount of " + amt + " dollars.");
    float balance = balance();
    if(balance < 0)
      throw new bank.UnknownException("Could not retrieve balance for Account " + _accNum);
    balance += amt;
    try
    {
      DBUtils.setBalance(_accNum,balance);
    }
    catch(Exception e)
    {
      throw new bank.UnknownException(e.getMessage());
    }
  }
  public void withdraw (float amt) throws bank.InvalidOperationException, bank.UnknownException
  {
    if(amt <= (float)0.0)
     throw new InvalidOperationException("Invalid withdrawal amount of " + amt + " dollars.");
    float balance = balance();
    if(balance < 0)
      throw new bank.UnknownException("Could not retrieve balance for Account " + _accNum);
    if(amt > balance)
      throw new InvalidOperationException("Cannot withdraw more than account balance.");
    balance = balance() - amt;
    // Update the balance
    try
    {
      DBUtils.setBalance(_accNum,balance);
    }
    catch(Exception e)
    {
      throw new bank.UnknownException(e.getMessage());
    }
  }
}

Use a servant locator

When the POA has the NON_RETAIN policy, it uses servant managers called servant locators. Remember, the NON_RETAIN policy implies that the POA will not maintain the association between the servant instance and an activated object in the active object map. When the POA receives a request, it will extract the object ID from the request and always invoke the preinvoke() method on the registered servant locator. preinvoke() returns a servant to the POA, and the POA forwards the request to that servant. Once the request processes, the POA invokes the postinvoke() method on the servant locator, passing in the servant as one of the parameters. postinvoke() performs any clean up, and the servant ceases to exist as far as the POA is concerned.

As in the case of servant activators, the POA makes several guarantees to prevent race situations from occurring in multithreaded environments. For example, the request that causes preinvoke() to execute is the only request that will be processed using the servant returned by preinvoke(). Once that request is done, the POA will call postinvoke(). Furthermore, the same thread is used for both preinvoke() and postinvoke() calls for a given request.

Here is an example servant activator implementation that registers with the AccountPOA:

package bank;
import java.util.logging.Logger;
import org.omg.CORBA.*;
import org.omg.PortableServer.*;
import org.omg.PortableServer.ServantLocatorPackage.CookieHolder;
public class AccountServerLocatorImpl extends org.omg.PortableServer.ServantLocatorPOA
{
  public Servant preinvoke(byte[] oid, POA adapter,
      String operation, CookieHolder the_cookie) throws ForwardRequest
  {
    // First check the DB to make sure account exists.
    // Then create an accountimpl and return.
    System.out.println("(AccountServerLocatorImpl) Returning an Account Object for account number " 
            + new String(oid));
    try
    {
      DBUtils.getBalance(new String(oid));
    }
    catch(Exception e)
    {
      System.out.println("(AccountServerLocatorImpl) Oops... Invalid account number " + new String(oid));
      throw new org.omg.CORBA.OBJECT_NOT_EXIST();
    }
    return new AccountImpl(new String(oid));
  }
  public void postinvoke(byte[] oid, POA adapter,
      String operation, java.lang.Object the_cookie, Servant the_servant)
  {
  }
}

The preinvoke() method implementation resembles the servant activator's incarnate() method implementation. Similarly, postinvoke() is identical to etherialize() in the servant activator. The similarity does not end there. The account servant implementation is the same as well; it's the same class—AccountImpl—used by the servant activator.

Use a default servant

The final way to configure an AccountPOA is to use a default servant, which you register with the AccountPOA. It doesn't matter whether the AccountPOA has the RETAIN or NON_RETAIN policy. If it uses the RETAIN policy, then the AccountPOA uses the default servant only when a servant is not in the active object map. If the AccountPOA uses NON_RETAIN, then all requests forward to the default servant. The default servant must be thread safe, that is, it must not hold an object-specific state. An implementation of an account default servant is shown below:

package bank;
import org.omg.CORBA.*;
import org.omg.PortableServer.*;
public class AccountDefaultServantImpl extends bank.AccountPOA
{
  public AccountDefaultServantImpl()
  {
  }
  public float balance()
  {
    try
    {
      return DBUtils.getBalance(accountNumber());
    }
    catch(Exception e)
    {
      return (float)-1.0;
    }
  }
  public String accountNumber()
  {
    return new String(this._object_id());
  }
  public bank.AccountType accountType ()
  {
    try
    {
      return DBUtils.getAccountType(accountNumber());
    }
    catch(Exception e)
    {
      return AccountType.NIL;
    }
  }
  public void deposit (float amt) throws bank.InvalidOperationException, bank.UnknownException
  {
    if(amt <= (float)0.0)
     throw new InvalidOperationException("Invalid deposit amount of " + amt + " dollars.");
    float balance = balance();
    if(balance < 0)
      throw new bank.UnknownException("Could not retrieve balance for Account " + accountNumber());
    balance += amt;
    try
    {
      DBUtils.setBalance(accountNumber(),balance);
    }
    catch(Exception e)
    {
      throw new bank.UnknownException(e.getMessage());
    }
  }
  public void withdraw (float amt) throws bank.InvalidOperationException, bank.UnknownException
  {
    if(amt <= (float)0.0)
     throw new InvalidOperationException("Invalid withdrawal amount of " + amt + " dollars.");
    float balance = balance();
    if(balance < 0)
      throw new bank.UnknownException("Could not retrieve balance for Account " + accountNumber());
    if(amt > balance)
      throw new InvalidOperationException("Cannot withdraw more than account balance.");
    balance = balance() - amt;
    // Update the balance
    try
    {
      DBUtils.setBalance(accountNumber(),balance);
    }
    catch(Exception e)
    {
      throw new bank.UnknownException(e.getMessage());
    }
  }
}

The above implementation differs from the other two solutions' account servants (AccountImpl instances) in that no account number member variable is in the default account servant class. To compensate for the lack of an account number member variable, the default servant class has two major changes, which are outlined below:

  1. Instead of returning the value of the private account member variable (_accNum), the accountNumber() method creates a new account number based on the object ID. It retrieves this ID with the public _object_id() method that is part of the org.omg.PortableServer.Servant class—one of AccountPOA's base classes (automatically generated by idlj). The _object_id is actually a shortcut for calling PortableServer::Current::get_object_id. The Current interface provides methods for retrieving the current request's context. All ORBs provide an implementation of the Current interface that can be retrieved by calling the resolve_initial_references() method on the ORB with the parameter POACurrent. Since the default servant will be used for many (or all) requests, being able to retrieve the context information, such as the object ID, proves important.
  2. All methods that previously used the private account member variable (_accNum) now use the accountNumber()method.

The finishing touch: Create the server process

Now that we have implemented the bank and account servants, we must code a server process that will create the necessary POA hierarchy with the correct policies installed in each POA. Follow four main steps to create this process:

  1. Create the BankPOA: Regardless of the solution used for the implementations' Account piece, this step remains the same:

      // Create the BankPOA
      Policy[] policies = new Policy[3];
      policies[0] = rootPOA.create_lifespan_policy(LifespanPolicyValue.PERSISTENT);
      policies[1] = 
        rootPOA.create_request_processing_policy(RequestProcessingPolicyValue.
              USE_ACTIVE_OBJECT_MAP_ONLY );
      policies[2] = rootPOA.create_servant_retention_policy(ServantRetentionPolicyValue.
              RETAIN);
      POA bankPOA = rootPOA.create_POA("BankPOA",null,policies);
    

    The BankPOA creates persistent object references and uses the active object map only with the default RETAIN policy.

  2. Create the AccountPOA: The AccountPOA policies vary depending on which of the three solutions you use.

    Here is the code for creating AccountPOA if a servant activator is used:

      // Create the AccountPOA and set its ServantActivator
      policies[0] = rootPOA.create_lifespan_policy(LifespanPolicyValue.PERSISTENT);
      policies[1] = 
        rootPOA.create_request_processing_policy(RequestProcessingPolicyValue.
              USE_SERVANT_MANAGER);
      policies[2] = rootPOA.create_servant_retention_policy(ServantRetentionPolicyValue.
              RETAIN);
      POA accountPOA = rootPOA.create_POA("AccountPOA",null,policies);
      AccountServerActivatorImpl asa = new bank.AccountServerActivatorImpl();
      rootPOA.activate_object(asa);
      accountPOA.set_servant_manager(asa._this(orb));
    

    Here is the code for creating AccountPOA if you choose the servant locator solution:

      // Create the AccountPOA and set its ServantLocator (NON_RETAIN 
    Policy)
      policies[0] = rootPOA.create_lifespan_policy(LifespanPolicyValue.PERSISTENT);
      policies[1] = 
        rootPOA.create_request_processing_policy(RequestProcessingPolicyValue.
              USE_SERVANT_MANAGER);
      policies[2] = rootPOA.create_servant_retention_policy(ServantRetentionPolicyValue.
              NON_RETAIN);
      POA accountPOA = rootPOA.create_POA("AccountPOA",null,policies);
      AccountServerLocatorImpl asl = new bank.AccountServerLocatorImpl();
      rootPOA.activate_object(asl);
      accountPOA.set_servant_manager(asl._this(orb));
    

    And here is the code for creating AccountPOA if you use a default servant:

      // Create the AccountPOA and set its default servant (NON_RETAIN Policy)
      policies[0] = 
    rootPOA.create_lifespan_policy(LifespanPolicyValue.PERSISTENT);
      policies[1] = 
        rootPOA.create_request_processing_policy(RequestProcessingPolicyValue.
              USE_DEFAULT_SERVANT);
      policies[2] = rootPOA.create_servant_retention_policy(ServantRetentionPolicyValue.
              NON_RETAIN);
      POA accountPOA = rootPOA.create_POA("AccountPOA",null,policies);
      AccountDefaultServantImpl defaultAcc = new bank.AccountDefaultServantImpl();
      rootPOA.activate_object(defaultAcc);
      accountPOA.set_servant(defaultAcc);
    
  3. Make the bank object reference available to clients: The BankPOA creates a bank reference with the object ID CORBABank. This reference is then provided to clients in the CORBA naming service under three names: BankServer, BankServer2, and BankServer3 corresponding to the servant activator, servant locator, and default servant solutions, respectively. We provide the reference to clients the same way we did for Part 2's Hello World example.

    The source code's BankServer.java shows the code for the server that uses the servant activator with AccountPOA. Listing BankServer2.java shows the code for the server that uses the servant locator with AccountPOA. Listing BankServer3.java shows the code for the server that uses the default servant with AccountPOA.

  4. Activate the POAs and wait for client requests: Once again, no rocket science here. We activate the POA the same way we did in Part 2's Hello World example.
Page 3 of 3

Use servertool to register the CORBA servers

Since the bank and account object references are persistent references, J2SE 1.4 requires me to register my server implementations using servertool, which comes bundled with J2SE 1.4. The relevant commands are shown below. Note that these commands assume that J2SE 1.4 is installed on the E drive in a folder called j2sdk1.4.0_01 and that the bank example source code is compiled into the E:\dev\Corba\Bank\classes directory:

orbd -ORBInitialPort 1050 -ORBInitialHost localhost
E:\j2sdk1.4.0_01\bin\servertool.exe orbd -ORBInitialPort 1050 -ORBInitialHost localhost
register -server bank.BankServer -applicationName BankServer -classpath E:\dev\Corba\Bank    classes -args "-ORBInitialPort 1050 -ORBInitialHost localhost"
register -server bank.BankServer2 -applicationName BankServer2 -classpath E:\dev\Corba\Bank\classes -args "-ORBInitialPort 1050 -ORBInitialHost localhost"
register -server bank.BankServer3 -applicationName BankServer3 -classpath E:\dev\Corba\Bank\classes -args "-ORBInitialPort 1050 -ORBInitialHost localhost"

As shown in the code above, I start ORBD (Object Request Broker Daemon), which is the J2SE 1.4 persistent naming service, and then use servertool to register BankServer, BankServer2, and BankServer3. Each registration command requires me to provide the classpath and any startup command-line parameters. Obviously, you will have to compile the Java source code before you can register. Compiling (running idlj and javac) the bank application proves no different from compiling Part 2's Hello World application. So I'll leave that as an exercise for you.

A test client

BankClient, shown below, is a generic client that can access and test all three solutions discussed above:

package bank.test;
import bank.Bank;
import bank.BankHelper;
import bank.Account;
import bank.AccountHelper;
import bank.AccountInformation;
import bank.AccountType;
import org.omg.CORBA.*;
import org.omg.CosNaming.*;
import org.omg.CosNaming.NamingContextPackage.*;
public class BankClient
{
  public static void main(String args[])
  {
    try
    {
      // Create and initialize the ORB
        ORB orb = ORB.init(args, null);
      // Get the root naming context
      org.omg.CORBA.Object objRef =
        orb.resolve_initial_references("NameService");
        
      // Use NamingContextExt instead of NamingContext. This is
      // part of the Interoperable Naming Service.
      NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
      // Resolve the object reference in Naming
      String name = "BankServer";
      System.out.println(args.length);
      if(args.length == 5)
        name = args[4];
      Bank theBank = BankHelper.narrow(ncRef.resolve_str(name));
      System.out.println("Obtained a handle on server object: " + theBank);
      AccountInformation accInfo = new AccountInformation();
      accInfo.accNum="";
      accInfo.accType = AccountType.NIL;
      Account[] accounts = theBank.findAccounts(accInfo);
      for(int i=0; i<accounts.length; i++)
        System.out.println("Account Number " + ((Account)accounts[i]).accountNumber());
      accounts[0].deposit((float)1.1);
    }
    catch(bank.UnknownException e)
    {
      System.out.print(e.reason);
    }
    catch(bank.InvalidOperationException e)
    {
      System.out.print(e.reason);
    }
    catch (Exception e)
    {
      System.out.println("ERROR : " + e) ;
        e.printStackTrace(System.out);
    }
  }
}

The program takes one parameter: the server to access, which is BankServer, BankServer2, or BankServer3. The default is BankServer. This parameter is actually the fifth one on the command line since the first four are J2SE 1.4 ORB-related parameters as shown in the example command line below:

  java test.BankClient -ORBInitialHost localhost -ORBInitialPort 1050 BankServer2

The program then finds all accounts and deposits .10 in the first account found. So, before you run this program, ensure you have at least one account in your database.

Until next time

As promised, this article has been hands-on, with wide coverage of various POA programming aspects. Based on the knowledge gained from Parts 2 and 3 of this series, you should be in a good position to start using the POA in your own projects. I will conclude the series in Part 4, another hands-on article, where I cover two important topics: the Interoperable Naming Service and portable interceptors.

Tarak Modi has been architecting scalable, high-performance, distributed applications for more than seven years and is currently a senior specialist with North Highland, a management and technology consulting company. His professional experience includes hardcore C++ and Java programming; working with Microsoft technologies such as COM (Component Object Model), MTS (Microsoft Transaction Server), and COM+; Java-based technologies including J2EE; and CORBA. He is also coauthor of Professional Java Web Services (Wrox Press, 2002; ISBN 1861003757). To find out more about him, visit his personal Website at http://www.tekNirvana.com.