Day 6 Implementing Basic Application Capabilities

来源:互联网 发布:2016欧洲杯网络转播权 编辑:程序博客网 时间:2024/03/28 22:08


Teach Yourself CORBA In 14 Days

Previous chapterNext chapterContents 


Day 6
Implementing Basic Application Capabilities

  • Implementing Basic Bank Server Capabilities
    • Implementing the BankServer Interface
    • Implementing the Bank Interface
    • Implementing the Account Interface
    • Implementing the CheckingAccount Interface
    • Implementing the SavingsAccount Interface
  • Implementing Basic Client Capabilities
    • Implementing the Customer Interface
    • Implementing Additional Client Functionality
  • Running the Examples
    • Starting the CORBA Naming Service
    • Starting the BankServer Component
    • Starting the Bank Component
    • Running the Client Application
  • Summary
  • Q&A
  • Workshop
    • Quiz
    • Exercise

 


On Day 5, "Designing the System: A Crash Course in Object-Oriented Analysis and Design," you mapped an application design to a set of IDL interfaces that defined the structure on which that design would be realized. Today you'll implement those interfaces, thus creating an operational set of servers and clients that implement the basic capabilities of the Bank application. You'll enhance the application with additional functionality in future chapters, but in this chapter you'll concentrate on implementing the core set of features of the application.

 


Note:The examples in this chapter have been developed using Visigenic Software's (http://www.visigenic.com/) VisiBroker/C++ product. Despite the existence of a standard IDL language mapping for C++, various inconsistencies still exist between CORBA products. If you are using a different product, such as IONA Technologies' Orbix, you might need to modify the sample code slightly, although these changes will be minimal. Consult your product documentation for language mapping information if you experience difficulty compiling the examples.

Implementing Basic Bank Server Capabilities

The server functionality of the Bank application is encapsulated in three main interfaces: the BankServer, the Bank, and the Account. The Account interface is subdivided into two derived interfaces, CheckingAccount and SavingsAccount. This set of interfaces defines the core functionality of the Bank application. After you provide implementations for these interfaces, you move on to implement the client capabilities as well. The sole interface implemented by the client is the Customer interface, used by the client to access various bank services.

Implementing the BankServer Interface

The first server interface to implement is the BankServer. Recall that the purpose of the BankServer is to enable clients to locate Bank objects. BankServer objects, in turn, are located by clients and Bank objects through the CORBA Naming Service or another similar mechanism. When a Bank object is created, it locates and registers with a BankServer; in the same fashion, when the Bank object is ready to shut down, it unregisters with the BankServer.

The IDL for the BankServer interface (from Day 5) is defined in Listing 6.1.

Listing 6.1. BankServer.idl.

 1: // BankServer.idl   2:    3: #ifndef BankServer_idl   4: #define BankServer_idl   5:    6: #include "Bank.idl"   7:    8: // A BankServer provides clients with visibility to Bank objects.   9: interface BankServer {  10:   11:     // Register the given Bank with this BankServer. The Bank will  12:     // be listed by getBanks() until unregisterBank() is called with  13:     // that Bank.  14:     void registerBank(in Bank bank);  15:   16:     // Unregister the given Bank from this BankServer. If the Bank  17:     // was not previously registered, this operation does nothing.  18:     void unregisterBank(in Bank bank);  19:   20:     // Return a list of all Banks currently registered with this  21:     // BankServer.  22:     BankList getBanks();  23: };  24:   25: #endif 

It is up to you to provide implementations for the registerBank(), unregisterBank(), and getBanks() methods, as well as the constructor (or constructors) and destructor for this class.

Examining BankServerImpl.h in Listing 6.2, notice first (in line 10) that the BankServerImpl class extends the _sk_BankServer class. _sk_BankServer is the server skeleton for the BankServer interface. If you were to examine the source file for this class, you would see that it provides pure virtual methods corresponding to the IDL methods you defined earlier. Because it is a skeleton, though, it doesn't provide any implementations for these methods; that is the job of the BankServerImpl class. Also, note that the name BankServerImpl was chosen arbitrarily; you can name the class whatever you want, but it is recommended that you devise and follow a naming convention for your implementation classes.

Listing 6.2. BankServerImpl.h.

 1: // BankServerImpl.h   2:    3: #ifndef BankServerImpl_h   4: #define BankServerImpl_h   5:    6: #include <vector>   7:    8: #include "../BankServer_s.h"   9:   10: class BankServerImpl : public _sk_BankServer {  11:   12: public:  13:   14:     // Constructor.  15:     BankServerImpl();  16:   17:     // Destructor.  18:     ~BankServerImpl();  19:   20:     // These methods are described in BankServer.idl.  21:     virtual void registerBank(Bank_ptr bank);  22:     virtual void unregisterBank(Bank_ptr bank);  23:     virtual BankList* getBanks();  24:   25: private:  26:   27:     // This BankServer's list of Banks.  28:     std::vector<Bank_ptr> myBanks;  29: };  30:   31: #endif     Also, notice the following:
#include <vector>  

and

// This BankServer's list of Banks.  std::vector<Bank_ptr> myBanks;  

If you guessed that the implementation utilizes C++'s Standard Template Library (STL), you are correct. Most modern C++ compilers include STL; if yours doesn't, you can either obtain an implementation of STL or modify the sample code to avoid STL. One source for STL implementations is ObjectSpace (at http://www.objectspace.com/), which provides an STL implementation for many platforms and compilers free of charge.

Further examining BankServerImpl.h, you'll see that the IDL methods defined previously map to the following C++ methods:

virtual void registerBank(Bank_ptr bank);  virtual void unregisterBank(Bank_ptr bank);  virtual BankList* getBanks();  

Notice in particular that the Bank references are mapped to the Bank_ptr type, and the BankList to BankList*. Other than these changes and the appearance of the virtual keyword (which is unnecessary for CORBA implementation classes but usually preferable), the C++ method definitions strongly resemble their IDL counterparts.

Listing 6.3 contains the implementation class BankServerImpl.cpp, which provides the implementation for the _sk_BankServer interface.

Listing 6.3. BankServerImpl.cpp.

 1: // BankServerImpl.cpp   2:    3: #include "BankServerImpl.h"   4:    5: #include <algorithm>   6: #include <functional>   7:    8: // STL-derived unary function which returns TRUE if Banks are equal.   9: class IsBankEqual : public std::unary_function<Bank_ptr, bool> {  10: public:  11:     IsBankEqual(argument_type bank) { myBank = bank; }  12:     result_type operator()(argument_type bank) { return bank->  13:             _is_equivalent(myBank) != 0; }  14: private:  15:     argument_type myBank;  16: };  17:   18: // Constructor.  19: BankServerImpl::BankServerImpl() : myBanks() {  20:   21: }  22:   23: // Destructor.  24: BankServerImpl::~BankServerImpl() {  25:   26: }  27:   28: void BankServerImpl::registerBank(Bank_ptr bank) {  29:   30:     // Add the given Bank at the end of the list.  31:     cout << "BankServerImpl: Registering Bank /"" << bank->name() <<  32:             "/"." << endl;  33:     myBanks.push_back(Bank::_duplicate(bank));  34: }  35:   36: void BankServerImpl::unregisterBank(Bank_ptr bank) {  37:   38:     std::vector<Bank_ptr>::iterator first = myBanks.begin();  39:     std::vector<Bank_ptr>::iterator last = myBanks.end();  40:     IsBankEqual predicate(bank);  41:   42:     std::vector<Bank_ptr>::iterator matchedBank = std::  43:             find_if(first, last, predicate);  44:     if (matchedBank == last) {  45:   46:         // Invalid Bank; do nothing.  47:         cout << "BankServerImpl: Ignored attempt to unregister "  48:                 "invalid Bank." << endl;  49:         return;  50:     }  51:     cout << "BankServerImpl: Unregistering Bank /"" << bank->name()  52:             << "/"." << endl;  53:   54:     // Delete the given Bank.  55:     myBanks.erase(matchedBank);  56:     bank->_release();  57: }  58:   59: BankList* BankServerImpl::getBanks() {  60:   61:     BankList* list = new BankList(myBanks.size());  62:     CORBA::Long i;  63:   64:     for (i = 0; i < myBanks.size(); i++) {  65:         (*list)[i] = Bank::_duplicate(myBanks[i]);  66:     }  67:   68:     return list;  69: } 

Of particular interest in this class are the following highlights:

BankServerImpl.cpp makes use of STL-provided algorithms and functions, as evidenced by the #include directives in lines 5 and 6.

The IsBankEqual class, occupying lines 8 through 16, is an encapsulation of a function that compares two Bank references for equality (that is, they both refer to the same Bank object). The equality test is performed through the _is_equivalent() method, which returns a TRUE (nonzero) result if its object and the argument object indeed refer to the same object.

 


Note:According to the CORBA specification, _is_equivalent() might actually return FALSE even if two object references are equivalent. The only guarantee made by the CORBA specification is that if _is_ equivalent() returns TRUE, then the object references are equivalent; otherwise, they may or may not be equivalent. (See the CORBA specification document for more information.)

The registerBank() method (lines 28-34) simply adds the given Bank to the BankServerImpl's internal list of Banks (in the myBanks member). Note the use of the Bank class's _duplicate() method, which increments the reference count of the given Bank object by one. This indicates to the Bank object that the BankServerImpl intends to retain a reference to that object. The reference count for an object, in turn, simply maintains a count of outstanding references to that object. (You'll see later how the BankServerImpl releases its reference to the Bank object.)

Now you'll examine the unregisterBank() method in several parts.

The first part of unregisterBank(), in lines 36-52, iterates through the BankServerImpl's internal list of Banks (again, stored in the myBanks member). The isBankEqual class discussed earlier is used to determine equality of Bank references; in this example, the std::find_if() method uses an isBankEqual object to compare object references. This step is necessary so that when a Bank is unregistered, the BankServerImpl can remove it from its internal list of Banks.

In the last part of unregisterBank(), lines 54-56, the given Bank is first removed from myBanks, via the erase() method. Then BankServerImpl indicates to the Bank that it is no longer keeping a reference to that Bank by calling the _release() method. _release() is the counterpart to the _duplicate() method mentioned previously; _release() decrements the reference count of an object. When that object's reference count reaches zero, the object can be (but not necessarily) destroyed. Because the BankServerImpl increments a Bank's reference count when it registers and decrements the Bank's reference count when it unregisters, the net change of the Bank's reference count after it has registered and later unregistered is zero.

On its own, the BankServerImpl class doesn't do anything useful. To realize its capability, you must provide code that creates a BankServerImpl and makes it available to other objects on the network. This is done in BankServerMain.cpp, which appears in Listing 6.4.

Listing 6.4. BankServerMain.cpp.

 1: // BankServerMain.cpp   2:    3: #include "BankServerImpl.h"   4: #include <iostream.h>   5:    6: int main(int argc, char *const *argv) {   7:    8:     // Initialize the ORB and BOA.   9:     CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);  10:     CORBA::BOA_var boa = orb->BOA_init(argc, argv);  11:   12:     // Create a BankServerImpl object.  13:     BankServerImpl bankServer;  14:   15:     // Notify the BOA that the BankServerImpl object is ready.  16:     boa->obj_is_ready(&bankServer);  17:   18:     // Wait for CORBA events.  19:     cout << "BankServer ready." << endl;  20:     boa->impl_is_ready();  21:   22:     // When this point is reached, the application is finished.  23:     return 0;  24: } 

The first thing a CORBA application must do is initialize its environment, that is, its Object Request Broker (ORB) and Basic Object Adapter (BOA). This is accomplished through the ORB_init() and BOA_init() methods in lines 8-10.

After the ORB and BOA are initialized, other CORBA objects can be created and made available to the network. This is done on a per-object basis using the obj_is_ready() method (in lines 15-16).

The preceding code notifies the BOA that the BankServerImpl object is ready to be used by other objects on the network. The BOA also provides the impl_is_ready() method, which you see used in lines 18-20.

The impl_is_ready() method notifies the BOA that the application is ready to receive events. Typically, impl_is_ready() will wait for events for an implementation-dependent period of time; usually, this is configurable by the application developer. For instance, impl_is_ready() can process events until the application is interrupted, or it can terminate the application after a predetermined amount of time has elapsed--an hour, for instance--without any events being received.

Now that the BankServer interface has been implemented, you can turn your attention to the interface that interacts with the BankServer: the Bank interface.

Implementing the Bank Interface

The Bank interface, as you recall from Day 5, describes the services provided by a Bank--generally, the manipulation of Accounts within that Bank. The IDL for the Bank interface is defined as shown in Listing 6.5.

Listing 6.5. Bank.idl.

 1: // Bank.idl   2:    3: // Forward declaration of Bank interface.   4: interface Bank;   5:    6: #ifndef Bank_idl   7: #define Bank_idl   8:    9: // sequence of Banks  10: typedef sequence<Bank> BankList;  11:   12: #include "Customer.idl"  13: #include "Account.idl"  14:   15: // A Bank provides access to Accounts. It can create an Account  16: // on behalf of a Customer, delete an Account, or list the current  17: // Accounts with the Bank.  18: interface Bank {  19:   20:     // This Bank's name.  21:     attribute string name;  22:   23:     // This Bank's address.  24:     attribute string address;  25:   26:     // Create an Account on behalf of the given Customer, with the  27:     // given account type ("savings" or "checking", where case is  28:     // significant), and the given opening balance.  29:     Account createAccount(in Customer customer, in string  30:             accountType, in float openingBalance);  31:   32:     // Delete the given Account. If the Account is not with this  33:     // Bank, this operation does nothing.  34:     void deleteAccount(in Account account);  35:   36:     // List all Accounts with this Bank.  37:     AccountList getAccounts();  38: };  39:  40: #endif 

Here, you'll need to provide implementations for name() and address()--which have both accessor and mutator forms for the name and address attributes--along with createAccount(), deleteAccount(), and getAccounts(), as well as the constructor (or constructors) and destructor for this class.

After looking at BankServerImpl.h (back in Listing 6.2), nothing in BankImpl.h should be too surprising (see Listing 6.6). Again, the mapping of IDL methods to C++ methods is straightforward (although you'll notice the use of the CORBA::Float type that the IDL float type mapped to), and the BankImpl makes use of STL in much the same way as BankServerImpl.

Listing 6.6. BankImpl.h.

 1: // BankImpl.h   2:    3: #ifndef BankImpl_h   4: #define BankImpl_h   5:    6: #include <vector>   7:    8: #include "../Bank_s.h"   9:   10: class BankImpl : public _sk_Bank {  11:   12: public:  13:   14:     // Constructor.  15:     //  16:     // name - This Bank's name.  17:     BankImpl(const char* name);  18:   19:     // Destructor.  20:     ~BankImpl();  21:   22:     // These methods are described in Bank.idl.  23:     virtual char* name();  24:     virtual void name(const char* val);  25:     virtual char* address();  26:     virtual void address(const char* val);  27:     virtual Account_ptr createAccount(Customer_ptr customer,  28:             const char* accountType, CORBA::Float openingBalance);  29:     virtual void deleteAccount(Account_ptr account);  30:     virtual AccountList* getAccounts();  31:   32: protected:  33:   34:     // Return the next available account number. The result is  35:     // returned in a static buffer.  36:     char* getNextAccountNumber();  37:   38:     // Return the current date in the form "Mmm DD YYYY". The result  39:     // is returned in a static buffer.  40:     char* getCurrentDate();  41:   42: private:  43:   44:     // Default constructor.  45:     BankImpl();  46:   47:     // This Bank's name.  48:     char* myName;  49:   50:     // This Bank's address.  51:     char* myAddress;  52:   53:     // This Bank's Accounts.  54:     std::vector<Account_ptr> myAccounts;  55:   56:     // The next available account number.  57:     unsigned int myNextAccountNumber;  58: };  59:   60: #endif 

You haven't seen it yet, but BankMain.cpp defines a global variable called boa (see line 14 of Listing 6.7), which is a reference to the Basic Object Adapter used by the application. Although the simplicity of a global boa variable makes it appropriate for a sample application, in a production application you want a cleaner mechanism for sharing the reference to the BOA. For example, you can provide a class that makes the BOA available through a static member, or you can write class constructors to take a BOA as an argument. Regardless of how you accomplish this, there will sometimes be a need for various objects in an application to access the BOA. (In this example, a BankImpl needs to call obj_is_ready() on Account objects that it creates.)

Listing 6.7. BankImpl.cpp.

  1: // BankImpl.cpp    2:     3: #include "BankImpl.h"    4:     5: #include <time.h>    6: #include <string.h>    7: #include <iostream.h>    8: #include <algorithm>    9: #include <functional>   10:    11: #include "SavingsAccountImpl.h"   12: #include "CheckingAccountImpl.h"   13:    14: extern CORBA::BOA_var boa;   15:    16: // STL-derived unary function which returns TRUE if Accounts are   17: // equal.   18: class IsAccountEqual : public std::unary_function<Account_ptr,   19:         bool> {   20: public:   21:     IsAccountEqual(argument_type account) { myAccount = account; }   22:     result_type operator()(argument_type account) { return account->   23:             _is_equivalent(myAccount) != 0; }   24: private:   25:     argument_type myAccount;   26: };   27:    28: // Constructor.   29: //   30: // name - This Bank's name.   31: BankImpl::BankImpl(const char* name) : myAccounts(),   32:         myName(strdup(name)), myAddress(strdup("123 Elm Street, "   33:         "Anyware USA 12345")), myNextAccountNumber(0) {   34:    35: }   36:    37: // Default constructor.   38: BankImpl::BankImpl() : myAccounts(), myName(NULL), myAddress(NULL),   39:         myNextAccountNumber(0) {   40:    41: }   42:    43: // Destructor.   44: BankImpl::~BankImpl() {   45:    46:     cout << "Bank /"" << name() << "/" being destroyed." << endl;   47:     free(myName);   48:     free(myAddress);   49: }   50:    51: char* BankImpl::name() {   52:    53:     return CORBA::strdup(myName);   54: }   55:    56: void BankImpl::name(const char* val) {   57:    58:     free(myName);   59:     myName = strdup(val);   60: }   61:    62: char* BankImpl::address() {   63:    64:     return CORBA::strdup(myAddress);   65: }   66:    67: void BankImpl::address(const char* val) {   68:    69:     free(myAddress);   70:     myAddress = strdup(val);   71: }   72:    73: Account_ptr BankImpl::createAccount(Customer_ptr customer,   74:         const char* accountType, CORBA::Float openingBalance) {   75:    76:     Account_ptr newAccount;   77:    78:     if (strcmp(accountType, "savings") == 0) {   79:    80:         // Create a new SavingsAccountImpl object for the Account.   81:         cout << "BankImpl: Creating new SavingsAccount for "   82:                 "Customer " << customer->name() << "." << endl;   83:         newAccount = new SavingsAccountImpl(getNextAccountNumber(),   84:                 getCurrentDate(), openingBalance, customer, 10.0);   85:     } else if (strcmp(accountType, "checking") == 0) {   86:    87:         // Create a new CheckingAccountImpl object for the Account.   88:         cout << "BankImpl: Creating new CheckingAccount for "   89:                 "Customer " << customer->name() << "." << endl;   90:         newAccount = new CheckingAccountImpl(getNextAccountNumber(),   91:                 getCurrentDate(), openingBalance, customer);   92:     } else {   93:    94:         // Invalid Account type; do nothing.   95:         cout << "BankImpl: Customer " << customer->name() <<   96:                 " requested invalid Account type /"" << accountType   97:                 << "/"." << endl;   98:         return Account::_nil();   99:     }  100:   101:     // Add the created Account at the end of the list and return it.  102:     ::boa->obj_is_ready(newAccount);  103:     myAccounts.push_back(Account::_duplicate(newAccount));  104:     return newAccount;  105: }  106:   107: void BankImpl::deleteAccount(Account_ptr account) {  108:   109:     std::vector<Account_ptr>::iterator first = myAccounts.begin();  110:     std::vector<Account_ptr>::iterator last = myAccounts.end();  111:     IsAccountEqual predicate(account);  112:   113:     std::vector<Account_ptr>::iterator matchedAccount = std::  114:             find_if(first, last, predicate);  115:     if (matchedAccount == last) {  116:   117:         // Invalid Account; do nothing.  118:         cout << "BankImpl: Ignored attempt to delete invalid " <<  119:                 "Account." << endl;  120:         return;  121:     }  122:     cout << "BankImpl: Deleting Account /"" << account->  123:             accountNumber() << "/"." << endl;  124:   125:     // Delete the given Account.  126:     myAccounts.erase(matchedAccount);  127:     account->_release();  128: }  129:   130: AccountList* BankImpl::getAccounts() {  131:   132:     AccountList* list = new AccountList(myAccounts.size());  133:     CORBA::Long i;  134:   135:     for (i = 0; i < myAccounts.size(); i++) {  136:         (*list)[i] = Account::_duplicate(myAccounts[i]);  137:     }  138:   139:     return list;  140: }  141:   142: // Return the next available account number. The result is returned  143: // in a static buffer.  144: char* BankImpl::getNextAccountNumber() {  145:   146:     static char accountNumber[16] = "Account        ";  147:   148:     sprintf(accountNumber + 7, "%08u", myNextAccountNumber++);  149:   150:     return accountNumber;  151: }  152:   153: // Return the current date in the form "Mmm DD YYYY". The result is  154: // returned in a static buffer.  155: char* BankImpl::getCurrentDate() {  156:   157:     static char currentDate[12] = "           ";  158:   159:     time_t ltime;  160:     time(&ltime);  161:     char* ctimeResult = ctime(&ltime);  162:   163:     memcpy(currentDate, ctimeResult + 4, 3);  164:     memcpy(currentDate + 4, ctimeResult + 8, 2);  165:     memcpy(currentDate + 7, ctimeResult + 20, 4);  166:   167:     return currentDate;  168: } 

Here are the highlights from BankImpl.cpp (refer to Listing 6.7).

Notice that when a string is returned by a CORBA method, as in the first form of the name() method (see lines 51-54), it must be done in the proper manner. When a CORBA method returns a string, it must use the CORBA::strdup() method (note that this is not the same as the standard library strdup() method) on that string. Using CORBA::strdup() allows the application to free the memory used by the string after it has been marshaled back to the caller. The preceding example demonstrates this for the name() accessor method; you will notice that the address() accessor method is similar.

Also, examine the last few lines of the createAccount() method (see lines 101-104).

Notice that when a new Account object is created, you must inform the BOA that the object is ready, again using the obj_is_ready() method. (This is why the BankImpl object needs visibility to the BOA.) Note also that before the newly created Account object is passed back to the caller, its reference count is incremented by the _duplicate() method. This is important because when an object reference is passed back to a caller (either as a return value or as an out or inout parameter), the reference count is decremented. Therefore, when returning a CORBA object reference in this manner, you must always _duplicate() the object reference before returning it.

The remainder of BankImpl.cpp will be recognized by C++ programmers or remembered from BankServerImpl.cpp. Like BankServerImpl, the BankImpl must also be accompanied by a bit of extra code to start up the BankImpl and make it available to the rest of the network. This code can be seen in Listing 6.8.

Listing 6.8. BankMain.cpp.

 1: // BankMain.cpp   2:    3: #include "BankImpl.h"   4:    5: #include <iostream.h>   6:    7: #include "../BankServer_c.h"   8:    9: CORBA::BOA_var boa;  10:   11: int main(int argc, char *const *argv) {  12:   13:     // Check the number of arguments; there should be exactly one  14:     // (two counting the executable name itself).  15:     if (argc != 2) {  16:         cout << "Usage: Bank <bankname>" << endl;  17:         return 1;  18:     }  19:   20:     // Assign the bank name to the first argument.  21:     const char* bankName = argv[1];  22:   23:     // Initialize the ORB and BOA.  24:     CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);  25:     ::boa = orb->BOA_init(argc, argv);  26:   27:     // Create a Bank object.  28:     BankImpl bank(bankName);  29:   30:     // Notify the BOA that the BankImpl object is ready.  31:     ::boa->obj_is_ready(&bank);  32:   33:     // Locate a BankServer object and register with it.  34:     BankServer_var bankServer;  35:     try {  36:         bankServer = BankServer::_bind();  37:     } catch (const CORBA::Exception& ex) {  38:   39:         // The bind attempt failed...  40:         cout << "BankImpl: Unable to bind to a BankServer." << endl;  41:         cout << ex << endl;  42:         return 1;  43:     }  44:     try {  45:         bankServer->registerBank(&bank);  46:     } catch (const CORBA::Exception& ex) {  47:   48:         // The registerBank() attempt failed...  49:         cout << "BankImpl: Unable to register Bank." << endl;  50:         cout << ex << endl;  51:         return 1;  52:     }  53:   54:     // Wait for CORBA events.  55:     cout << "Bank /"" << bankName << "/" ready." << endl;  56:     ::boa->impl_is_ready();  57:   58:     // When this point is reached, the application is finished.  59:     return 0;  60: } 

A key difference between BankServerMain.cpp and BankMain.cpp is that, whereas a BankServer doesn't need to locate and connect to other objects, a Bank needs to locate a BankServer and register with it. This is accomplished by the code in lines 33-43.

The BankServer::_bind() call attempts to bind, or connect, to a BankServer object. Optionally, _bind() can specify a name of an object to connect to, but when the name is omitted, _bind() will attempt to connect to any available object of the requested type. If the _bind() attempt fails, a CORBA::Exception is thrown, then caught, and its contents printed to the console.

 


Note:Although the _bind() functionality is available in several ORB products (including IONA Technologies' Orbix and Visigenic's VisiBroker products), it is not included in the CORBA standard. In any case, the _bind() mechanism is probably unsuitable for large-scale production systems anyway; you'll most likely want to use the CORBA Naming Service or Trader Service to locate objects on the network. (See Day 12 for a more in-depth discussion of the CORBAservices.)

If the application successfully binds to a BankServer object, it will register the Bank with it, as in lines 44-52.

Here, registerBank() is the remote method of the BankServer interface. As with all remote methods, registerBank() can throw a CORBA::Exception, and thus this exception should be caught by the application. In this case, the exception is caught and an error message printed.

A Bank object is essentially a factory for Account objects, and the implementations of the Account and its derived interfaces are what you will study next.

Implementing the Account Interface

The Account interface defines the capabilities of a generic bank account, such as the withdrawal and deposit of funds. The IDL for the Account interface is defined as shown in Listing 6.9.

Listing 6.9. Account.idl.

 1: // Account.idl   2:    3: // Forward declaration of Account interface.   4: interface Account;   5:    6: #ifndef Account_idl   7: #define Account_idl   8:    9: // sequence of Accounts  10: typedef sequence<Account> AccountList;  11:   12: #include "Customer.idl"  13:   14: // An Account is an entity owned by a Bank and held by a Customer  15: // (or multiple Customers). An Account has a balance which can be  16: // affected by deposits and withdrawals.  17: interface Account {  18:   19:     // This Account's account number.  20:     readonly attribute string accountNumber;  21:   22:     // This Account's creation date.  23:     readonly attribute string creationDate;  24:   25:     // This Account's current balance.  26:     readonly attribute float balance;  27:   28:     // Return a list of Customers who hold this Account.  29:     CustomerList getCustomers();  30:   31:     // Withdraw the given amount from this Account. Returns the new  32:     // account balance.  33:     float withdraw(in float amount);  34:   35:     // Deposit the given amount into this Account. Returns the new  36:     // account balance.  37:     float deposit(in float amount);  38: };  39:   40: #endif 

Thus, you'll need to provide implementations for the following methods: accountNumber(), creationDate(), and balance(), which are accessors for the accountNumber, creationDate, and balance attributes, respectively, as well as getCustomers(), withdraw(), and deposit(), along with the constructor (or constructors) and destructor for this class. The header file for the implementation (AccountImpl.h) appears in Listing 6.10, followed by the implementation itself (AccountImpl.cpp) in Listing 6.11.

Listing 6.10. AccountImpl.h.

 1: // AccountImpl.h   2:    3: #ifndef AccountImpl_h   4: #define AccountImpl_h   5:    6: #include "../Account_s.h"   7:    8: class AccountImpl : public _sk_Account {   9:   10: // Allow CheckingAccountImpl and SavingsAccountImpl access to the  11: // protected constructor.  12: friend class CheckingAccountImpl;  13: friend class SavingsAccountImpl;  14:   15: public:  16:   17:     // Destructor.  18:     ~AccountImpl();  19:   20:     // These methods are described in Account.idl.  21:     virtual char* accountNumber();  22:     virtual char* creationDate();  23:     virtual CORBA::Float balance();  24:     virtual CustomerList* getCustomers();  25:     virtual CORBA::Float withdraw(CORBA::Float amount);  26:     virtual CORBA::Float deposit(CORBA::Float amount);  27:   28: protected:  29:   30:     // Constructor.  31:     //  32:     // accountNumber - Account number.  33:     // creationDate - Account creation date.  34:     // initialBalance - Initial Account balance.  35:     // customer - Initial Account owner.  36:     AccountImpl(const char* accountNumber, const char* creationDate,  37:             CORBA::Float initialBalance, Customer_ptr customer);  38:   39: private:  40:   41:     // Default constructor.  42:     AccountImpl();  43:   44:     // This Account's account number.  45:     char* myAccountNumber;  46:   47:     // This Account's creation date.  48:     char* myCreationDate;  49:   50:     // This Account's current balance.  51:     CORBA::Float myBalance;  52:   53:     // This Account's owners.  54:     CustomerList myOwners;  55: };  56:   57: #endif

Listing 6.11. AccountImpl.cpp.

 1: // AccountImpl.cpp   2:    3: #include "AccountImpl.h"   4:    5: #include <string.h>   6:    7: // Constructor.   8: //   9: // accountNumber - Account number.  10: // creationDate - Account creation date.  11: // initialBalance - Initial Account balance.  12: // customer - Initial Account owner.  13: AccountImpl::AccountImpl(const char* accountNumber, const char*  14:         creationDate, CORBA::Float initialBalance, Customer_ptr  15:         customer) : myAccountNumber(strdup(accountNumber)),  16:         myCreationDate(strdup(creationDate)),  17:         myBalance(initialBalance), myOwners() {  18:   19:     // Add the Customer to the owner list.  20:     myOwners.length(1);  21:     myOwners[0] = Customer::_duplicate(customer);  22: }  23:   24: // Default constructor.  25: AccountImpl::AccountImpl() : myAccountNumber(NULL),  26:         myCreationDate(NULL), myBalance(0.0), myOwners() {  27:   28: }  29:   30: // Destructor.  31: AccountImpl::~AccountImpl() {  32:   33:     free(myAccountNumber);  34:     free(myCreationDate);  35: }  36:   37: char* AccountImpl::accountNumber() {  38:   39:     return CORBA::strdup(myAccountNumber);  40: }  41:   42: char* AccountImpl::creationDate() {  43:   44:     return CORBA::strdup(myCreationDate);  45: }  46:   47: CORBA::Float AccountImpl::balance() {  48:   49:     return myBalance;  50: }  51:   52: CustomerList* AccountImpl::getCustomers() {  53:   54:     return &myOwners;  55: }  56:   57: CORBA::Float AccountImpl::withdraw(CORBA::Float amount) {  58:   59:     myBalance -= amount;  60:   61:     return myBalance;  62: }  63:   64: CORBA::Float AccountImpl::deposit(CORBA::Float amount) {  65:   66:     myBalance += amount;  67:   68:     return myBalance;  69: }

Implementing the CheckingAccount Interface

The CheckingAccount interface is the easiest interface to implement because it doesn't define any additional methods. The IDL definition for the CheckingAccount interface is shown in Listing 6.12.

Listing 6.12. CheckingAccount.idl.

 1: // CheckingAccount.idl   2:    3: #ifndef CheckingAccount_idl   4: #define CheckingAccount_idl   5:    6: #include "Account.idl"   7:    8: // A CheckingAccount is an Account which supports checking. It   9: // does not gain any interest, as its sibling, the SavingsAccount,  10: // does.  11: interface CheckingAccount : Account {  12:   13: };  14:   15: #endif 

Again, because there are no attributes or methods defined as part of the CheckingAccount interface, there is little to do for the implementation class. Simply providing an empty constructor and destructor is sufficient. The implementation for the CheckingAccount interface can be seen in Listings 6.13 and 6.14.

Listing 6.13. CheckingAccountImpl.h.

 1: // CheckingAccountImpl.h   2:    3: #ifndef CheckingAccountImpl_h   4: #define CheckingAccountImpl_h   5:    6: #include "../CheckingAccount_s.h"   7: #include "AccountImpl.h"   8:    9: class CheckingAccountImpl : public _sk_CheckingAccount {  10:   11: public:  12:   13:     // Constructor.  14:     //  15:     // accountNumber - Account number.  16:     // creationDate - Account creation date.  17:     // initialBalance - Initial Account balance.  18:     // customer - Initial Account owner.  19:     CheckingAccountImpl(const char* accountNumber, const char*  20:             creationDate, CORBA::Float initialBalance, Customer_ptr  21:             customer);  22:   23:     // Destructor.  24:     ~CheckingAccountImpl();  25:   26:     // These methods are described in Account.idl.  27:     virtual char* accountNumber();  28:     virtual char* creationDate();  29:     virtual CORBA::Float balance();  30:     virtual CustomerList* getCustomers();  31:     virtual CORBA::Float withdraw(CORBA::Float amount);  32:     virtual CORBA::Float deposit(CORBA::Float amount);  33:   34: private:  35:   36:     // Default constructor.  37:     CheckingAccountImpl();  38:   39:     // My associated AccountImpl object.  40:     AccountImpl myAccount;  41: };  42:   43: #endif

Listing 6.14. CheckingAccountImpl.cpp.

 1: // CheckingAccountImpl.cpp   2:    3: #include "CheckingAccountImpl.h"   4:    5: // Constructor.   6: //   7: // accountNumber - Account number.   8: // creationDate - Account creation date.   9: // initialBalance - Initial Account balance.  10: // customer - Initial Account owner.  11: CheckingAccountImpl::CheckingAccountImpl(const char* accountNumber,  12:         const char* creationDate, CORBA::Float initialBalance,  13:         Customer_ptr customer) : myAccount(accountNumber,  14:         creationDate, initialBalance, customer) {  15:   16: }  17:   18: // Default constructor.  19: CheckingAccountImpl::CheckingAccountImpl() : myAccount(NULL, NULL,  20:         0.0, Customer::_nil()) {  21:   22: }  23:   24: // Destructor.  25: CheckingAccountImpl::~CheckingAccountImpl() {  26:   27: }  28:   29: char* CheckingAccountImpl::accountNumber() {  30:   31:     return myAccount.accountNumber();  32: }  33:   34: char* CheckingAccountImpl::creationDate() {  35:   36:     return myAccount.creationDate();  37: }  38:   39: CORBA::Float CheckingAccountImpl::balance() {  40:   41:     return myAccount.balance();  42: }  43:   44: CustomerList* CheckingAccountImpl::getCustomers() {  45:   46:     return myAccount.getCustomers();  47: }  48:   49: CORBA::Float CheckingAccountImpl::withdraw(CORBA::Float amount) {  50:   51:     return myAccount.withdraw(amount);  52: }  53:   54: CORBA::Float CheckingAccountImpl::deposit(CORBA::Float amount) {  55:   56:     return myAccount.deposit(amount);  57: }

Implementing the SavingsAccount Interface

The SavingsAccount interface is more complicated than the CheckingAccount interface and so requires a bit more effort to implement. The SavingsAccount IDL definition is shown in Listing 6.15.

Listing 6.15. SavingsAccount.idl.

 1: // SavingsAccount.idl   2:    3: #ifndef SavingsAccount_idl   4: #define SavingsAccount_idl   5:    6: #include "Account.idl"   7:    8: // A SavingsAccount is an Account which supports savings   9: // account semantics, such as gaining interest.  10: interface SavingsAccount : Account {  11:   12:     // This Account's interest rate.  13:     readonly attribute float interestRate;  14:   15:     // Set this Account's interest rate to the given rate.  16:     // Returns the previous rate.  17:     float setInterestRate(in float rate);  18: };  19:   20: #endif 

For the SavingsAccount implementation class, you need to define getinterestRate() (an accessor for the interestRate attribute), setInterestRate(), and the class constructor (or constructors) and destructor. The implementation class appears in Listings 6.16 and 6.17.

Listing 6.16. SavingsAccountImpl.h.

 1: // SavingsAccountImpl.h   2:    3: #ifndef SavingsAccountImpl_h   4: #define SavingsAccountImpl_h   5:    6: #include "../SavingsAccount_s.h"   7: #include "AccountImpl.h"   8:    9: class SavingsAccountImpl : public AccountImpl {  10:   11: public:  12:   13:     // Constructor.  14:     //  15:     // accountNumber - Account number.  16:     // creationDate - Account creation date.  17:     // initialBalance - Initial Account balance.  18:     // customer - Initial Account owner.  19:     // interestRate - Initial Account interest rate.  20:     SavingsAccountImpl(const char* accountNumber, const char*  21:             creationDate, CORBA::Float initialBalance, Customer_ptr  22:             customer, CORBA::Float interestRate);  23:   24:     // Destructor.  25:     ~SavingsAccountImpl();  26:   27:     // These methods are described in Account.idl.  28:     virtual char* accountNumber();  29:     virtual char* creationDate();  30:     virtual CORBA::Float balance();  31:     virtual CustomerList* getCustomers();  32:     virtual CORBA::Float withdraw(CORBA::Float amount);  33:     virtual CORBA::Float deposit(CORBA::Float amount);  34:   35:     // These methods are described in SavingsAccount.idl.  36:     virtual CORBA::Float interestRate();  37:     virtual CORBA::Float setInterestRate(CORBA::Float rate);  38:   39: private:  40:   41:     // Default constructor.  42:     SavingsAccountImpl();  43:   44:     // This Account's interest rate.  45:     CORBA::Float myInterestRate;  46:   47:     // My associated AccountImpl object.  48:     AccountImpl myAccount;  49: };  50:   51: #endif

Listing 6.17. SavingsAccountImpl.cpp.

 1: // SavingsAccountImpl.cpp   2:    3: #include "SavingsAccountImpl.h"   4:    5: // Constructor.   6: //   7: // accountNumber - Account number.   8: // creationDate - Account creation date.   9: // initialBalance - Initial Account balance.  10: // customer - Initial Account owner.  11: // interestRate - Initial Account interest rate.  12: SavingsAccountImpl::SavingsAccountImpl(const char* accountNumber,  13:         const char* creationDate, CORBA::Float initialBalance,  14:         Customer_ptr customer, CORBA::Float interestRate) :  15:         myAccount(accountNumber, creationDate, initialBalance,  16:         customer), myInterestRate(interestRate) {  17:   18: }  19:   20: // Default constructor.  21: SavingsAccountImpl::SavingsAccountImpl() : myAccount(NULL, NULL,  22:         0.0, Customer::_nil()), myInterestRate(0.0) {  23:   24: }  25:   26: // Destructor.  27: SavingsAccountImpl::~SavingsAccountImpl() {  28:   29: }  30:   31: char* SavingsAccountImpl::accountNumber() {  32:   33:     return myAccount.accountNumber();  34: }  35:   36: char* SavingsAccountImpl::creationDate() {  37:   38:     return myAccount.creationDate();  39: }  40:   41: CORBA::Float SavingsAccountImpl::balance() {  42:   43:     return myAccount.balance();  44: }  45:   46: CustomerList* SavingsAccountImpl::getCustomers() {  47:   48:     return myAccount.getCustomers();  49: }  50:   51: CORBA::Float SavingsAccountImpl::withdraw(CORBA::Float amount) {  52:   53:     return myAccount.withdraw(amount);  54: }  55:   56: CORBA::Float SavingsAccountImpl::deposit(CORBA::Float amount) {  57:   58:     return myAccount.deposit(amount);  59: }  60:   61: CORBA::Float SavingsAccountImpl::interestRate() {  62:   63:     return myInterestRate;  64: }  65:   66: CORBA::Float SavingsAccountImpl::setInterestRate(CORBA::Float rate) {  67:   68:     CORBA::Float oldInterestRate = myInterestRate;  69:   70:     myInterestRate = rate;  71:   72:     return oldInterestRate;  73: }

Implementing Basic Client Capabilities

Now that you have implemented the basic capabilities of the CORBA server for the Bank application, you're ready to begin working on the basic client capabilities. Since most of the work is done by the server in this application, you'll find the client to be fairly simple by comparison.

Implementing the Customer Interface

The Customer interface encapsulates the attributes and behaviors of a Customer of a Bank. For the most part, this interface serves as a container for Account objects. The IDL definition for the Customer interface is shown in Listing 6.18.

Listing 6.18. Customer.idl.

 1: // Customer.idl   2:    3: // Forward declaration of Customer interface.   4: interface Customer;   5:    6: #ifndef Customer_idl   7: #define Customer_idl   8:    9: // sequence of Customers  10: typedef sequence<Customer> CustomerList;  11:   12: #include "Account.idl"  13:   14: // A Customer can hold one or more Accounts. Presumably, the  15: // Customer is what drives the rest of this application.  16: interface Customer {  17:   18:     // This Customer's name.  19:     attribute string name;  20:   21:     // This Customer's Social Security number.  22:     readonly attribute string socialSecurityNumber;  23:   24:     // This Customer's address.  25:     attribute string address;  26:   27:     // This Customer's mother's maiden name.  28:     readonly attribute string mothersMaidenName;  29:   30:     // Return a list of Accounts held (or co-held) by this  31:     // Customer.  32:     AccountList getAccounts();  33: };  34:   35: #endif 

You need to implement getname(), setname(), getsocialSecurityNumber(), getaddress(), setAddress(), and getmothersMaidenName(), which are the accessors and mutators for various attributes of Account, along with getAccounts() and the class constructor (or constructors) and destructor. The implementation for CustomerImpl appears in Listings 6.19 and 6.20.

Listing 6.19. CustomerImpl.h.

 1: // CustomerImpl.h   2:    3: #ifndef CustomerImpl_h   4: #define CustomerImpl_h   5:    6: #include "../Customer_s.h"   7:    8: class CustomerImpl : public _sk_Customer {   9:   10: public:  11:   12:     // Constructor.  13:     //  14:     // name - Customer's name.  15:     // socialSecurityNumber - Customer's Social Security number.  16:     // address - Customer's address.  17:     // mothersMaidenName - Customer's mother's maiden name.  18:     CustomerImpl(const char* name, const char* socialSecurityNumber,  19:             const char* address, const char* mothersMaidenName);  20:   21:     // Destructor.  22:     ~CustomerImpl();  23:   24:     // These methods are described in Customer.idl.  25:     virtual char* name();  26:     virtual void name(const char* val);  27:     virtual char* socialSecurityNumber();  28:     virtual char* address();  29:     virtual void address(const char* val);  30:     virtual char* mothersMaidenName();  31:     virtual AccountList* getAccounts();  32:   33: private:  34:   35:     // Default constructor.  36:     CustomerImpl();  37:   38:     // This Customer's name.  39:     char* myName;  40:   41:     // This Customer's Social Security number.  42:     char* mySocialSecurityNumber;  43:   44:     // This Customer's address.  45:     char* myAddress;  46:   47:     // This Customer's mother's maiden name.  48:     char* myMothersMaidenName;  49:   50:     // This Customer's Accounts.  51:     AccountList myAccounts;  52: };  53:   54: #endif

Listing 6.20. CustomerImpl.cpp.

 1: // CustomerImpl.cpp   2:    3: #include "CustomerImpl.h"   4:    5: #include <string.h>   6:    7: // Constructor.   8: //   9: // name - Customer's name.  10: // socialSecurityNumber - Customer's Social Security number.  11: // address - Customer's address.  12: // mothersMaidenName - Customer's mother's maiden name.  13: CustomerImpl::CustomerImpl(const char* name, const char*  14:         socialSecurityNumber, const char* address, const char*  15:         mothersMaidenName) : myName(strdup(name)),  16:         mySocialSecurityNumber(strdup(socialSecurityNumber)),  17:         myAddress(strdup(address)),  18:         myMothersMaidenName(strdup(mothersMaidenName)),  19:         myAccounts() {  20:   21: }  22:   23: // Default constructor.  24: CustomerImpl::CustomerImpl() : myName(NULL),  25:         mySocialSecurityNumber(NULL), myAddress(NULL),  26:         myMothersMaidenName(NULL), myAccounts() {  27:   28: }  29:   30: // Destructor.  31: CustomerImpl::~CustomerImpl() {  32:   33:     free(myName);  34:     free(mySocialSecurityNumber);  35:     free(myAddress);  36:     free(myMothersMaidenName);  37: }  38:   39: char* CustomerImpl::name() {  40:   41:     return CORBA::strdup(myName);  42: }  43:   44: void CustomerImpl::name(const char* val) {  45:   46:     free(myName);  47:     myName = strdup(val);  48: }  49:   50: char* CustomerImpl::socialSecurityNumber() {  51:   52:     return CORBA::strdup(mySocialSecurityNumber);  53: }  54:   55: char* CustomerImpl::address() {  56:   57:     return CORBA::strdup(myAddress);  58: }  59:   60: void CustomerImpl::address(const char* val) {  61:   62:     free(myAddress);  63:     myAddress = strdup(val);  64: }  65:   66: char* CustomerImpl::mothersMaidenName() {  67:   68:     return CORBA::strdup(myMothersMaidenName);  69: }  70:   71: AccountList* CustomerImpl::getAccounts() {  72:   73:     return &myAccounts;  74: }

Implementing Additional Client Functionality

In addition to the Customer interface, any useful client needs to implement additional functionality. An examination of the Customer implementation will suggest to you why this is so: Although the implementation allows a Customer to interact with various other components of the Bank application (such as Bank and Account objects), there is no functionality in the Customer implementation that directs the client application to actually do something. Therefore, any client application will not consist solely of the Customer interface implementation but will add extra functionality that performs useful work.

Listing 6.21 contains a sample client application that creates a new Customer object, opens a new Account with a Bank, and then performs some operations on that Account (a deposit and a withdrawal). This application is very simple and not very interactive (it obtains all of its parameters from the command line), but it demonstrates how operations on CORBA objects are invoked.

Listing 6.21. NewCustomerMain.cpp.

  1: // NewCustomerMain.cpp    2:     3: #include <iostream.h>    4:     5: #include "../Customer/CustomerImpl.h"    6:     7: #include "../Bank_c.h"    8:     9: int main(int argc, char *const *argv) {   10:    11:     // Check the number of arguments; there should be exactly four   12:     // (five counting the executable name itself).   13:     if (argc != 5) {   14:         cout << "Usage: NewCustomer <name> <social security number>"   15:                 " <address> <mother's maiden name>" << endl;   16:         return 1;   17:     }   18:    19:     // Assign the command line arguments to the Customer attributes.   20:     const char* name = argv[1];   21:     const char* socialSecurityNumber = argv[2];   22:     const char* address = argv[3];   23:     const char* mothersMaidenName = argv[4];   24:    25:     // Initialize the ORB and BOA.   26:     CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);   27:     CORBA::BOA_var boa = orb->BOA_init(argc, argv);   28:    29:     // Create a Customer object.   30:     cout << "NewCustomer: Creating new Customer:" << endl;   31:     cout << "  name: " << name << endl;   32:     cout << "  Social Security number: " << socialSecurityNumber <<   33:             endl;   34:     cout << "  address: " << address << endl;   35:     cout << "  mother's maiden name: " << mothersMaidenName << endl;   36:     CustomerImpl customer(name, socialSecurityNumber, address,   37:             mothersMaidenName);   38:    39:     // Notify the BOA that the CustomerImpl object is ready.   40:     boa->obj_is_ready(&customer);   41:    42:     // Locate a Bank object and register with it.   43:     Bank_var bank;   44:     try {   45:         bank = Bank::_bind();   46:     } catch (const CORBA::Exception& ex) {   47:    48:         // The bind attempt failed...   49:         cout << "NewCustomer: Unable to bind to a Bank." << endl;   50:         cout << ex << endl;   51:         return 1;   52:     }   53:    54:     // Do some cool stuff.   55:    56:     cout << "NewCustomer: Connected to Bank /"" << bank->name() <<   57:             "/"." << endl;   58:     Account_var account;   59:    60:     try {   61:         account = bank->createAccount(&customer, "checking", 0.0);   62:     } catch (const CORBA::Exception& ex) {   63:    64:         // The createAccount() attempt failed...   65:         cout << "NewCustomer: Unable to create Account." << endl;   66:         cout << ex << endl;   67:         return 1;   68:     }   69:    70:     try {   71:    72:         // Print out some Account statistics.   73:         cout << "NewCustomer: Opened new Account:" << endl;   74:         cout << "  account number: " << account->accountNumber() <<   75:                 endl;   76:         cout << "  creation date: " << account->creationDate() <<   77:                 endl;   78:         cout << "  account balance: " << account->balance() << endl;   79:    80:         // Perform some transactions on the Account.   81:         cout << "NewCustomer: Performing transactions." << endl;   82:         cout << "  Depositing $250.00; new balance is ___FCKpd___23quot; <<   83:                 account->deposit(250.00) << endl;   84:         cout << "  Withdrawing $500.00; new balance is ___FCKpd___23quot; <<   85:                 account->withdraw(500.00) << " (Whoops!)" << endl;   86:    87:         // Get rid of the Account.   88:         bank->deleteAccount(account);   89:     } catch (const CORBA::Exception& ex) {   90:    91:         // Some operation on the Account failed...   92:         cout << "NewCustomer: Error accessing Account." << endl;   93:         cout << ex << endl;   94:         return 1;   95:     }   96:    97:     // When this point is reached, the application is finished.   98:     return 0;  99: }

Running the Examples

Now that you've implemented all the server and client components of the Bank application, you're ready to try it out. Running the application consists of the following steps:

1. Start the CORBA Naming Service--or a similar type of service--which will be required by the BankServer when it starts up.

2
. Start the BankServer server. The BankServer will register itself with the Naming Service.

3
. Start one or more Bank servers. Upon startup, each Bank server will locate a BankServer through the Naming Service and register itself with the BankServer.

4
. Run the NewCustomer client application. The NewCustomer application will create a new Customer and Account, verifying that the Bank and BankServer objects are working correctly.

Starting the CORBA Naming Service

Generally, before starting any CORBA applications, you first need to invoke the mechanism by which the application components can find each other. Sometimes this is a CORBA Naming Service (which you'll use later); other times it's a simple executable included with the CORBA product you are using. Because the method by which these executables are invoked varies from one product to the next, you'll want to consult your product's documentation to determine how this is done.

For Visigenic's VisiBroker, you'll want to run the provided osagent utility. Running the utility is simple; at the command line, simply type the following:

osagent  

The osagent utility produces no output, but it begins a new process (which appears as an icon on the taskbar if you're using Windows 95 or Windows NT 4.0). The process--often referred to as a daemon in UNIX-speak--is called the ORBeline Smart Agent and allows CORBA applications using VisiBroker to locate each other.

 


Note:As directed in the VisiBroker documentation, you'll want to ensure that the VisiBroker bin directory is in your system's PATH environment variable. (The method for adding directories to the PATH varies between systems; consult the VisiBroker or system documentation to determine how this is accomplished.) Other products work similarly; it's often convenient to place the software's bin directory into your system's PATH.

Starting the BankServer Component

After you start the Naming Service (or ORBeline Smart Agent, in the case of VisiBroker), begin the BankServer application component. The BankServer must be run before any other components of the application because Customers need to connect to Banks, which in turn need to connect to BankServers.

To start the BankServer, first change to the directory where the BankServer executable resides. (If you're using Microsoft's Visual C++, the compiler by default places the executable in a directory under the project directory called Debug or Release, depending on the version of the executable you compiled.) Type the following at the command prompt:

BankServer  

You will see this output:

BankServer ready.  

The BankServer is now running and listening for incoming requests; you can now advance to starting a Bank server component.

Starting the Bank Component

To start the Bank component, open a separate command window. (UNIX users can "background" the BankServer application and use the same window, but for the sake of clarity, application components should be run in separate windows, at least for now).

To start the Bank component, change to the directory where the Bank executable is located, as you did with the BankServer component. Then choose a name for the Bank (any name will do), for example, "First Bank." The name of the Bank can contain spaces, but if it does, you have to quote the name so the application perceives it as a single parameter. Using the name "First Bank," type the following at the command prompt:

Bank "First Bank"  

Like the BankServer component, the Bank component does not produce much output at this time--simply the following:

Bank "First Bank" ready.  

The Bank component is now ready for client components to connect to it, so you're ready for the next step.

Running the Client Application

Again, open a new command window and change to the directory containing the executable to start the client application component. For the NewCustomer component, you need the following Customer information for parameters: name, Social Security number, address, and mother's maiden name. Again, parameters can contain spaces, as long as you surround each parameter with quotations. For example:

NewCustomer "Jeremy Rosenberger" 123456789 "123 Main Street,   Denver, CO 12345" Stroustrup  

The output of the client component will be the following:

NewCustomer: Creating new Customer:    name: Jeremy Rosenberger    Social Security number: 123456789    address: 123 Main Street, Denver, CO 12345    mother's maiden name: Stroustrup  NewCustomer: Connected to Bank "First Bank".  NewCustomer: Opened new Account:    account number: Account00000000    creation date: Sep 28 1997    account balance: 0  NewCustomer: Performing transactions.    Depositing $250.00; new balance is $250    Withdrawing $500.00; new balance is $-250 (Whoops!)  

When you get this far, congratulations! You have successfully developed and run a fully functional CORBA application.

Summary

In this chapter you started with an IDL specification, implemented the interfaces described in that specification, and created server and client executables that communicated by using those interfaces. Along the way you learned about some other aspects of CORBA applications:

  • CORBA provides the _duplicate() and _release() methods to track usage of CORBA objects. Although CORBA doesn't specify it, reference counting is a typical mechanism which is realized by the use of these methods.

  • CORBA servers indicate that their objects are ready (or the server itself is ready) by calling the obj_is_ready() and impl_is_ready() methods in the Basic Object Adapter (BOA).

  • CORBA clients connect to server objects by binding to them, using the _bind() method (a nonstandard method used in the examples in this chapter) or via an appropriate CORBAservice (which you'll explore later on Day 12). After an object is bound, the client can call methods on that object just as if the object were local.

Next you'll concentrate on enhancing the Bank example by adding new functionality and robustness to the application components. On Day 7, you'll add exception handling code to the application. Exceptions allow the application more flexibility in handling error conditions--a concept familiar to C++ and Java programmers. For this application, it would be useful if an attempt to withdraw funds from an Account that did not have such funds available signalled an error. (Recall from the example output that in the current application, withdrawing from an Account with insufficient funds simply makes the Account balance go negative.) One task you'll take on in the next chapter is to add an exception which traps this condition.

Q&A

Q Why do CORBA objects need to be reference counted?

A
In a non-distributed application, it is a simple matter to determine when an object is no longer required (in other words, no longer referenced by any other object) and thus destroy it, removing it from memory. In fact, some languages, such as Java, provide this functionality--known as garbage collection--as a feature built into the language. Distributed applications, however, make the issue of destroying unused objects more complicated because it is difficult to determine which objects are in use by other (potentially remote) objects. CORBA thus provides the reference counting mechanism to facilitate the tracking of object usage. Another method of achieving this is for each object to occasionally "ping" the objects it references, updating reference counts as necessary. However, this approach can be problematic, depending on the number of objects in the system.

Q Okay, I understand reference counting now, but what if a client application crashes and thus _release() is never called? Is the object never destroyed?

A
Indeed, the reference counting system employed by CORBA is not without its flaws, and this is one of them. If a client application that references a remote object crashes, _release() will not be called enough times on that object, and thus the object's reference count will never reach zero. There are design patterns (which you'll learn about on Day 10) that deal with issues such as this; the basic approach is for servers to evict objects into a database or persistent store after they have been unused for a preset period of time. If the object is required again, it can be retrieved from the persistent store or re-created in some other fashion.

Workshop

The following section will help you test your comprehension of the material presented in this chapter and put what you've learned into practice. You'll find the answers to the quiz and exercise in Appendix A.

Quiz

1. It was noted earlier that _is_equivalent() is not guaranteed to return TRUE when two object references refer to the same object. Can you think of a mechanism that would more reliably determine whether two references refer to the same object? (For simplicity, assume the objects are of the same type.)

2
. What would happen if _release() were not called on an object which had earlier been _duplicate()d?

3
. Why does NewCustomerMain.cpp have a try ... catch (const CORBA::Exception& ex) block?

Exercise

Modify the client application so that it prints the names of the Customers who are associated with the Account that was created. (The single Customer printed should be the same Customer whose information was entered on the command line.)

 


Previous chapterNext chapterContents

Macmillan Computer Publishing USA 
 

© Copyright, Macmillan Computer Publishing. All rights reserved.

 
原创粉丝点击