Day 7 Using Exceptions to Perform Error Checking

来源:互联网 发布:淘宝steam充值卡便宜 编辑:程序博客网 时间:2024/04/26 17:29

Teach Yourself CORBA In 14 Days

Previous chapterNext chapterContents 


Day 7
Using Exceptions to Perform Error Checking

  • Defining Exceptions for the Application
    • Exceptions in BankServer
    • Exceptions in Bank
    • Exceptions in Account
    • Exceptions in CheckingAccount
    • Exceptions in SavingsAccount
    • Exceptions in Customer
  • Modifying Server IDL to Use Exceptions
  • Modifying Server Code to Throw Exceptions
    • BankServerImpl
    • AccountImpl
    • CheckingAccountImpl
    • SavingsAccountImpl
    • BankImpl
  • Modifying Client Code to Catch Exceptions
  • Running the Enhanced Example
  • Summary
  • Q&A
  • Workshop
    • Quiz
    • Exercises

 


On Day 6, you created a basic CORBA application from a set of IDL specifications. The application implemented some basic functionality but lacked robustness; for example, the client component of the application demonstrated that it was possible to withdraw from an Account an amount greater than the Account's balance (without any sort of overdraft capability). In this chapter, you'll enhance the application to better handle error conditions (such as attempting to withdraw too many funds from an Account).

Defining Exceptions for the Application

To intelligently add error-checking capability to the application, you need to go back through the design, look at each method, determine what error conditions can occur in each method, and determine whether the error condition should be handled by the method itself or thrown (raised in CORBA lingo) back to the client to be handled. In this section, you'll analyze each IDL interface again, determining what exceptions can be thrown and from where.

 



 

 


Note:When a method throws an exception back to its caller, CORBA refers to this action as raising an exception. However, many languages--particularly Java and C++--refer to this as throwing an exception. Because both terms have identical meaning and are commonly used, you will see them used interchangeably throughout this chapter.

Exceptions in BankServer

You'll start by adding exception-handling capability to the BankServer interface. For your review (and convenience), Listing 7.1 contains the original BankServer.idl from Day 6, which you'll then modify with the exception-handling definitions.

Listing 7.1. Original 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 

Starting your analysis with the BankServer interface, you can see that three methods can potentially throw exceptions. The first method, registerBank() (lines 11-14), can conceivably throw an exception if an attempt is made to register a Bank that is already registered. Make a note of this exception; you can call it InvalidBankException (it is a good practice to make exception names as self-explanatory as possible). Moving on to the next method, unregisterBank() (lines 16-18), you can see that it is possible that a Bank that was never registered with the BankServer can attempt to unregister. Similarly, this method might throw an InvalidBankException. Finally, getBanks() (lines 20-22) need not throw any exception; if no Banks are registered with the BankServer, it can simply return an empty BankList.

Your analysis of the BankServer interface has turned up two methods that raise exceptions. The new signatures for these methods, modified to raise the exceptions described previously, are as follows:

void registerBank(in Bank bank)          raises (InvalidBankException);  void unregisterBank(in Bank bank)          raises (InvalidBankException);  

Exceptions in Bank

You can now move on to the Bank interface, the next interface to which you'll be adding exception-handling capability. Again, the original Bank.idl listing from Day 6 reappears in Listing 7.2.

Listing 7.2. Original 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 

The Bank interface contains three methods: createAccount() (lines 26-30), which need not throw any exceptions; deleteAccount() (lines 32-34), which throws an exception if the specified Account object does not exist in the Bank; and getAccounts() (lines 36-37), which also need not throw any exceptions.

Again using a self-explanatory exception name, the modified deleteAccount() signature looks like this:

void deleteAccount(in Account account)          raises (InvalidAccountException);  

Exceptions in Account

Listing 7.3 contains the Account interface (again making a reappearance from Day 6), the next candidate for adding exception raising.

Listing 7.3. Original 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 

Analyzing the Account interface's methods, in lines 28-29 you encounter getCustomers(), which need not throw any exceptions (again, an empty list can be returned if there are no Customers, even though this should never happen). Lines 31-33 contain the withdraw() method, which throws an exception if insufficient funds are available in the specified account. Finally, deposit() (lines 35-37) throws an exception if an invalid amount is specified (for instance, a negative amount). Actually, withdraw() throws an exception when given an invalid amount, as well.

This analysis leads to the following method signatures:

float withdraw(in float amount)          raises (InvalidAmountException,          InsufficientFundsException);  float deposit(in float amount)          raises (InvalidAmountException);  

Exceptions in CheckingAccount

Because the CheckingAccount interface adds no new methods to the Account interface, no additional analysis is necessary to determine exception raising for this interface. For your review, the listing for CheckingAccount.idl reappears in Listing 7.4.

Listing 7.4. Original CheckingAccount.idl.

// CheckingAccount.idl  #ifndef CheckingAccount_idl  #define CheckingAccount_idl  #include "Account.idl"  // A CheckingAccount is an Account which supports checking. It  // does not gain any interest, as its sibling, the SavingsAccount,  // does.  interface CheckingAccount : Account {  };  #endif

Exceptions in SavingsAccount

Listing 7.5 reproduces the SavingsAccount.idl listing from Day 6.

Listing 7.5. Original 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 

The SavingsAccount interface defines one additional method, setInterestRate() (lines 15-17), which can throw an exception if an invalid rate (for example, a negative one) is specified. In the interest of not using too many different exception names, you can reuse the InvalidAmountException from the preceding Account interfaces:

float setInterestRate(in float rate)         raises (InvalidAmountException);  

Exceptions in Customer

The Customer interface defines only one method, getAccounts(), which need not raise any exceptions. For your review, the original Customer.idl appears in Listing 7.6.

Listing 7.6. Original 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

Modifying Server IDL to Use Exceptions

Much of the work of adding exceptions has already been done (adding the raises clauses to the methods that throw exceptions). However, you did not yet define the exceptions themselves. For the sake of simplicity, define all exceptions in a single file--Exceptions.idl, which can be #included from other IDL files. Exceptions.idl appears in Listing 7.7.

Listing 7.7. Exceptions.idl.

 1: // Exceptions.idl   2:    3: #ifndef Exceptions_idl   4: #define Exceptions_idl   5:    6: // This exception is thrown when an invalid amount is passed to a   7: // method; for instance, if an account is asked to deposit a   8: // negative amount of funds.   9: exception InvalidAmountException {  10:   11: };  12:   13: // This exception is thrown when an invalid Account is passed to a  14: // method expecting an Account object.  15: exception InvalidAccountException {  16:   17: };  18:   19: // This exception is thrown when an invalid Bank is passed to a  20: // method expecting a Bank object.  21: exception InvalidBankException {  22:   23: };  24:   25: // This exception is thrown when there are insufficient funds to  26: // cover a transaction; for instance, if a withdrawal attempts to  27: // remove more funds than are available in an account.  28: exception InsufficientFundsException {  29:   30: };  31:   32: #endif 

Modifying each IDL file to reflect the changes is a simple matter. Listings 7.8-7.13 show the modified IDL files for each interface, with the changes from the original versions highlighted in bold.

Listing 7.8. Modified BankServer.idl.

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

Listing 7.9. Modified 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: #include "Exceptions.idl"  15:   16: // A Bank provides access to Accounts. It can create an Account  17: // on behalf of a Customer, delete an Account, or list the current  18: // Accounts with the Bank.  19: interface Bank {  20:   21:     // This Bank's name.  22:     attribute string name;  23:   24:     // This Bank's address.  25:     attribute string address;  26:   27:     // Create an Account on behalf of the given Customer, with the  28:     // given account type ("savings" or "checking", where case is  29:     // significant), and the given opening balance.  30:     Account createAccount(in Customer customer, in string  31:             accountType, in float openingBalance);  32:   33:     // Delete the given Account. If the Account is not with this  34:     // Bank, this operation does nothing.  35:     void deleteAccount(in Account account)  36:             raises (InvalidAccountException);  37:   38:     // List all Accounts with this Bank.  39:     AccountList getAccounts();  40: };  41:   42: #endif

Listing 7.10. Modified 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: #include "Exceptions.idl"  14:   15: // An Account is an entity owned by a Bank and held by a Customer  16: // (or multiple Customers). An Account has a balance which can be  17: // affected by deposits and withdrawals.  18: interface Account {  19:   20:     // This Account's account number.  21:     readonly attribute string accountNumber;  22:   23:     // This Account's creation date.  24:     readonly attribute string creationDate;  25:   26:     // This Account's current balance.  27:     readonly attribute float balance;  28:   29:     // Return a list of Customers who hold this Account.  30:     CustomerList getCustomers();  31:   32:     // Withdraw the given amount from this Account. Returns the new  33:     // account balance.  34:     float withdraw(in float amount)  35:             raises (InvalidAmountException,  36:             InsufficientFundsException);  37:   38:     // Deposit the given amount into this Account. Returns the new  39:     // account balance.  40:     float deposit(in float amount)  41:             raises (InvalidAmountException);  42: };  43:   44: #endif

Listing 7.11. Modified 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

Listing 7.12. Modified SavingsAccount.idl.

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

Listing 7.13. Modified 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 

After you have modified the IDL interface definitions to raise the proper exceptions in the proper methods, you can recompile the IDL files to generate new client stubs and server skeletons. When you have generated those, you can begin modifying the code to use the new exceptions.

Modifying Server Code to Throw Exceptions

The IDL interface definitions specify which exceptions can be thrown by which methods, but they don't specify when, or under what circumstances, those exceptions are thrown. Thus, after modifying the IDL definitions, you need to modify the server code to throw the proper exception at the proper time.

BankServerImpl

You can start with the simpler server, the BankServer. Recall that the BankServer interface contains two methods that raise exceptions: registerBank() and unregisterBank(). The changes need to be made in BankServerImpl.h as well as BankServerImpl.cpp as they appear in Listings 7.14 and 7.15, again highlighted in bold.

 


Note:In C++, it is legal for a method to throw an exception without declaring that it does so (with the throw clause in the method signature). However, this practice is considered poor style in some circles. It is recommended that all exceptions thrown by a C++ method be declared in that method's header; this makes it more apparent to the caller of the method that a particular set of exceptions might be raised. (Unlike C++, Java enforces this practice.)

Listing 7.14. Modified 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(const char* name);  16:   17:     // Destructor.  18:     ~BankServerImpl();  19:   20:     // These methods are described in BankServer.idl.  21:     virtual void registerBank(Bank_ptr bank) throw  22:             (InvalidBankException);  23:     virtual void unregisterBank(Bank_ptr bank) throw  24:             (InvalidBankException);  25:     virtual BankList* getBanks();  26:   27: private:  28:   29:     // Default constructor.  30:     BankServerImpl();  31:   32:     // This BankServer's list of Banks.  33:     std::vector<Bank_ptr> myBanks;  34: };  35:   36: #endif

Listing 7.15. Modified 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(const char* name) :  20:         _sk_BankServer(name), myBanks() {  21:   22: }  23:   24: // Destructor.  25: BankServerImpl::~BankServerImpl() {  26:   27: }  28:   29: void BankServerImpl::registerBank(Bank_ptr bank) throw  30:         (InvalidBankException) {  31:   32:     // First, ensure that the given Bank doesn't exist already.  33:     std::vector<Bank_ptr>::iterator first = myBanks.begin();  34:     std::vector<Bank_ptr>::iterator last = myBanks.end();  35:     IsBankEqual predicate(bank);  36:   37:     std::vector<Bank_ptr>::iterator matchedBank = std::  38:             find_if(first, last, predicate);  39:     if (matchedBank == last) {  40:   41:         // Bank was not found, so add the given Bank to the end of  42:         // the list.  43:         cout << "BankServerImpl: Registering Bank /"" << bank->  44:                 name() << "/"." << endl;  45:         myBanks.push_back(Bank::_duplicate(bank));  46:         return;  47:     } else {  48:   49:         // The Bank was already registered, so throw an exception.  50:         throw InvalidBankException();  51:   }  52: }  53:   54: void BankServerImpl::unregisterBank(Bank_ptr bank) throw  55:         (InvalidBankException) {  56:   57:     std::vector<Bank_ptr>::iterator first = myBanks.begin();  58:     std::vector<Bank_ptr>::iterator last = myBanks.end();  59:     IsBankEqual predicate(bank);  60:   61:     std::vector<Bank_ptr>::iterator matchedBank = std::  62:             find_if(first, last, predicate);  63:     if (matchedBank == last) {  64:   65:         // Invalid Bank; throw an exception.  66:         cout << "BankServerImpl: Attempted to unregister invalid "  67:                 "Bank." << endl;  68:         throw InvalidBankException();  69:     }  70:     cout << "BankServerImpl: Unregistering Bank /"" << bank->name()  71:             << "/"." << endl;  72:   73:     // Delete the given Bank.  74:     myBanks.erase(matchedBank);  75:     bank->_release();  76: }  77:   78: BankList* BankServerImpl::getBanks() {  79:   80:     BankList* list = new BankList(myBanks.size());  81:     CORBA::Long i;  82:   83:     for (i = 0; i < myBanks.size(); i++) {  84:         (*list)[i] = Bank::_duplicate(myBanks[i]);  85:     }  86:   87:     return list;  88: } 

Note that the pre-exception version of registerBank() did nothing when a client attempted to register a duplicate Bank object. The new and improved version, however, treats this as an error condition; a duplicate Bank registration results in an InvalidBankException being thrown. Similarly, unregisterBank() now throws an InvalidBankException when a client attempts to unregister a Bank that is not registered with the BankServer.

AccountImpl

Having dealt with the BankServer implementation, you can now turn your attention to the various interfaces contained in the Bank application, starting with AccountImpl.h and AccountImpl.cpp. As with BankServerImpl.h, modify the method signatures in AccountImpl.h to specify the exceptions thrown by AccountImpl methods. Similarly, AccountImpl.cpp will specify the conditions under which those exceptions are thrown. The modified AccountImpl.h and AccountImpl.cpp appear in Listings 7.16 and 7.17.

Listing 7.16. Modified 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) throw  26:         (InvalidAmountException, InsufficientFundsException);  27:     virtual CORBA::Float deposit(CORBA::Float amount) throw  28:         (InvalidAmountException);  29:   30: protected:  31:   32:     // Constructor.  33:     //  34:     // accountNumber - Account number.  35:     // creationDate - Account creation date.  36:     // initialBalance - Initial Account balance.  37:     // customer - Initial Account owner.  38:     AccountImpl(const char* accountNumber, const char*  39:             creationDate, CORBA::Float initialBalance, Customer_ptr  40:             customer);  41:   42: private:  43:   44:     // Default constructor.  45:     AccountImpl();  46:   47:     // This Account's account number.  48:     char* myAccountNumber;  49:   50:     // This Account's creation date.  51:     char* myCreationDate;  52:   53:     // This Account's current balance.  54:     CORBA::Float myBalance;  55:   56:     // This Account's owners.  57:     CustomerList myOwners;  58: };  59:   60: #endif

Listing 7.17. Modified AccountImpl.cpp.

 1: // AccountImpl.cpp   2:    3: #include "AccountImpl.h"   4:    5: #include <iostream.h>   6: #include <string.h>   7:    8: // Constructor.   9: //  10: // accountNumber - Account number.  11: // creationDate - Account creation date.  12: // initialBalance - Initial Account balance.  13: // customer - Initial Account owner.  14: AccountImpl::AccountImpl(const char* accountNumber, const char*  15:         creationDate, CORBA::Float initialBalance, Customer_ptr  16:         customer) : _sk_Account(accountNumber),  17:         myAccountNumber(strdup(accountNumber)),  18:         myCreationDate(strdup(creationDate)),  19:         myBalance(initialBalance), myOwners() {  20:   21:     // Add the Customer to the owner list.  22:     myOwners.length(1);  23:     myOwners[0] = Customer::_duplicate(customer);  24: }  25:   26: // Default constructor.  27: AccountImpl::AccountImpl() : myAccountNumber(NULL),  28:         myCreationDate(NULL), myBalance(0.0), myOwners() {  29:   30: }  31:   32: // Destructor.  33: AccountImpl::~AccountImpl() {  34:   35:     free(myAccountNumber);  36:     free(myCreationDate);  37: }  38:   39: char* AccountImpl::accountNumber() {  40:   41:     return CORBA::strdup(myAccountNumber);  42: }  43:   44: char* AccountImpl::creationDate() {  45:   46:     return CORBA::strdup(myCreationDate);  47: }  48:   49: CORBA::Float AccountImpl::balance() {  50:   51:     return myBalance;  52: }  53:   54: CustomerList* AccountImpl::getCustomers() {  55:   56:     return &myOwners;  57: }  58:   59: CORBA::Float AccountImpl::withdraw(CORBA::Float amount) throw  60:         (InvalidAmountException, InsufficientFundsException) {  61:   62:     // Disallow the withdrawal of negative amounts and throw an  63:     // exception if this is attempted.  64:     if (amount < 0.0) {  65:         cout << "AccountImpl: Attempted to withdraw invalid "  66:                 << "amount." << endl;  67:         throw InvalidAmountException();  68:     }  69:   70:     // Disallow withdrawal of an amount greater than the current  71:     // balance and throw an exception if this is attempted.  72:     if (amount > myBalance) {  73:         cout << "AccountImpl: Insufficient funds to withdraw "  74:                 << "specified amount." << endl;  75:         throw InsufficientFundsException();  76:     }  77:   78:     myBalance -= amount;  79:   80:     return myBalance;  81: }  82:   83: CORBA::Float AccountImpl::deposit(CORBA::Float amount) throw  84:         (InvalidAmountException) {  85:   86:     // Disallow the deposit of negative amounts and throw an  87:     // exception if this is attempted.  88:     if (amount < 0.0) {  89:         cout << "AccountImpl: Attempted to deposit invalid amount."  90:                 << endl;  91:         throw InvalidAmountException();  92:     }  93:   94:     myBalance += amount;  95:   96:     return myBalance;  97: } 

The modified withdraw() method in AccountImpl first checks the amount that the client wishes to withdraw. If the amount is negative, it is rejected by the withdraw() method, and an InvalidAmountException is thrown. If the amount is non-negative, the Account balance is checked to see whether sufficient funds are available to withdraw the requested amount. (Recall that overdraft protection is not a current feature of the system.) If there are insufficient funds to cover the withdrawal, withdraw() throws an InsufficientFundsException.

The deposit() method of AccountImpl works similarly; an InvalidAmountException is thrown if the caller attempts to deposit a negative amount into an Account (which would really be a withdrawal). Any amount can be deposited into an Account, so there is no need for deposit() to ever throw an InsufficientFundsException.

CheckingAccountImpl

Listings 7.18 and 7.19 contain further changes required to add exception capability to the Bank application.

Listing 7.18. Modified 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) throw  32:             (InvalidAmountException, InsufficientFundsException);  33:     virtual CORBA::Float deposit(CORBA::Float amount) throw  34:             (InvalidAmountException);  35:   36: private:  37:   38:     // Default constructor.  39:     CheckingAccountImpl();  40:   41:     // My associated AccountImpl object.  42:     AccountImpl myAccount;  43: };  44:   45: #endif

Listing 7.19. Modified 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:         throw (InvalidAmountException, InsufficientFundsException)  51:         {  52:   53:     return myAccount.withdraw(amount);  54: }  55:   56: CORBA::Float CheckingAccountImpl::deposit(CORBA::Float amount)  57:         throw (InvalidAmountException) {  58:   59:     return myAccount.deposit(amount);  60: } 

Again, the withdraw() and deposit() methods can throw the InvalidAmountException or InsufficientFundsException, or the InvalidAmountException, respectively. Note, however, that none of these exceptions are explicitly thrown within the methods themselves. Recall that the withdraw() and deposit() operations on the myAccount member (which is an AccountImpl object) can throw these exceptions. Because these exceptions are not caught by the methods in CheckingAccountImpl, the exceptions are simply passed back to the caller of the CheckingAccountImpl method.

SavingsAccountImpl

The changes for SavingsAccountImpl, as shown in Listings 7.20 and 7.21, closely resemble the changes made in CheckingAccountImpl.

Listing 7.20. Modified 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) throw  33:             (InvalidAmountException, InsufficientFundsException);  34:     virtual CORBA::Float deposit(CORBA::Float amount) throw  35:             (InvalidAmountException);  36:   37:     // These methods are described in SavingsAccount.idl.  38:        virtual CORBA::Float interestRate();  39:     virtual CORBA::Float setInterestRate(CORBA::Float rate) throw  40:             (InvalidAmountException);  41:   42: private:  43:   44:     // Default constructor.  45:     SavingsAccountImpl();  46:   47:     // This Account's interest rate.  48:     CORBA::Float myInterestRate;  49:   50:     // My associated AccountImpl object.  51:     AccountImpl myAccount;  52: };  53:   54: #endif

Listing 7.21. Modified 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:         throw (InvalidAmountException, InsufficientFundsException)  53:         {  54:   55:     return myAccount.withdraw(amount);  56: }  57:   58: CORBA::Float SavingsAccountImpl::deposit(CORBA::Float amount) throw  59:         (InvalidAmountException) {  60:   61:     return myAccount.deposit(amount);  62: }  63:   64: CORBA::Float SavingsAccountImpl::interestRate() {  65:   66:     return myInterestRate;  67: }  68:   69: CORBA::Float SavingsAccountImpl::setInterestRate(CORBA::Float rate) throw  70:         (InvalidAmountException) {  71:   72:     // Disallow negative interest rates and throw an exception if this is  73:     // attempted.  74:     if (rate < 0.0) {  75:   76:         throw InvalidAmountException();  77:     }  78:   79:     CORBA::Float oldInterestRate = myInterestRate;  80:   81:     myInterestRate = rate;  82:   83:     return oldInterestRate;  84: } 

The changes in the withdraw() and deposit() methods in SavingsAccountImpl copy their counterparts in CheckingAccountImpl exactly. In addition, the setInterestRate() method is modified to disallow negative interest rates (which would correspond to a savings account that loses money over time). If a client attempts to set a negative interest rate for the SavingsAccount, an InvalidAmountException is thrown.

BankImpl

The final server component to modify is the BankImpl itself. Only a single method, deleteAccount(), is modified to throw an exception. The modified BankImpl.h and BankImpl.cpp appear in Listings 7.22 and 7.23.

Listing 7.22. Modified 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) throw  30:             (InvalidAccountException);  31:     virtual AccountList* getAccounts();  32:   33: protected:  34:   35:     // Return the next available account number. The result is  36:     // returned in a static buffer.  37:     char* getNextAccountNumber();  38:   39:     // Return the current date in the form "Mmm DD YYYY". The result  40:     // is returned in a static buffer.  41:     char* getCurrentDate();  42:   43: private:  44:   45:     // Default constructor.  46:     BankImpl();  47:   48:     // This Bank's name.  49:     char* myName;  50:   51:     // This Bank's address.  52:     char* myAddress;  53:   54:     // This Bank's Accounts.  55:     std::vector<Account_ptr> myAccounts;  56:   57:     // The next available account number.  58:     unsigned int myNextAccountNumber;  59: };  60:   61: #endif

Listing 7.23. Modified 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) : _sk_Bank(name), myAccounts(),   32:         myName(strdup(name)), myAddress(strdup("123 Elm Street, "   33:         "Anywhere 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) throw  108:         (InvalidAccountException) {  109:   110:     std::vector<Account_ptr>::iterator first = myAccounts.begin();  111:     std::vector<Account_ptr>::iterator last = myAccounts.end();  112:     IsAccountEqual predicate(account);  113:   114:     std::vector<Account_ptr>::iterator matchedAccount = std::  115:             find_if(first, last, predicate);  116:     if (matchedAccount == last) {  117:   118:         // Invalid Account; throw an exception.  119:         cout << "BankImpl: Attempted to delete invalid Account." <<  120:                 endl;  121:         throw InvalidAccountException();  122:     }  123:     cout << "BankImpl: Deleting Account /"" << account->  124:             accountNumber() << "/"." << endl;  125:   126:     // Delete the given Account.  127:     myAccounts.erase(matchedAccount);  128:     account->_release();  129: }  130:   131: AccountList* BankImpl::getAccounts() {  132:   133:     AccountList* list = new AccountList(myAccounts.size());  134:     CORBA::Long i;  135:   136:     for (i = 0; i < myAccounts.size(); i++) {  137:         (*list)[i] = Account::_duplicate(myAccounts[i]);  138:     }  139:   140:     return list;  141: }  142:   143: // Return the next available account number. The result is returned  144: // in a static buffer.  145: char* BankImpl::getNextAccountNumber() {  146:   147:     static char accountNumber[16] = "Account        ";  148:   149:     sprintf(accountNumber + 7, "%08u", myNextAccountNumber++);  150:   151:     return accountNumber;  152: }  153:   154: // Return the current date in the form "Mmm DD YYYY". The result is  155: // returned in a static buffer.  156: char* BankImpl::getCurrentDate() {  157:   158:     static char currentDate[12] = "           ";  159:   160:     time_t ltime;  161:     time(&ltime);  162:     char* ctimeResult = ctime(&ltime);  163:   164:     memcpy(currentDate, ctimeResult + 4, 3);  165:     memcpy(currentDate + 4, ctimeResult + 8, 2);  166:     memcpy(currentDate + 7, ctimeResult + 20, 4);  167:   168:     return currentDate;  169: } 

The logic in deleteAccount() is identical to that seen previously in BankServerImpl's unregisterBank() method: If a client attempts to delete an Account that does not exist in the Bank, deleteAccount() throws an InvalidAccountException.

Congratulations--you have successfully completed the enhancements to the server side of the Bank application!

Modifying Client Code to Catch Exceptions

So far you've seen only half the picture. Now that the server code has been modified to throw exceptions on given occasions, the client code must be modified to handle those exceptions when they are raised. Although exception-handling code often permeates a client application, you'll see relatively little exception-handling code here, due to the simplicity of the Bank application's client.

First of all, note that no changes are required to CustomerImpl.h or CustomerImpl.cpp. Methods in the CustomerImpl class neither raise nor catch any exceptions, and thus no changes are necessary to these source files. However, NewCustomerMain.cpp--the end-user client application--needs to catch various exceptions. Just to make things interesting, NewCustomerMain.cpp performs some illegal actions on purpose, just to demonstrate the exception mechanism. The modified NewCustomerMain.cpp appears in Listing 7.24.

Listing 7.24. Modified 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___27quot; <<   83:                 account->deposit(250.00) << endl;   84:         cout << "  Withdrawing $500.00...; new balance is ___FCKpd___27quot;;   85:         try {   86:             cout << account->withdraw(500.00) << endl;   87:         } catch (const InsufficientFundsException&) {   88:    89:             // Insufficient funds were available for the withdraw()   90:             // operation.   91:             cout << endl << "NewCustomer: Exception caught: " <<   92:                     "Insufficient funds" << endl;   93:         }   94:    95:         // Get rid of the Account.   96:         cout << "  Deleting Account." << endl;   97:         bank->deleteAccount(account);   98:    99:         // Attempt to delete the Account again, just for kicks.  100:         // This should result in an exception being thrown.  101:         cout << "  Attempting to cause an exception by deleting" <<  102:                 " Account again." << endl;  103:         try {  104:             bank->deleteAccount(account);  105:         } catch (const InvalidAccountException&) {  106:   107:             // Sure enough, the exception was thrown.  108:             cout << "NewCustomer: Exception caught: Invalid " <<  109:                     "Account (as expected)" << endl;  110:         }  111:     } catch (const CORBA::Exception& ex) {  112:   113:         // Some operation on the Account failed...  114:         cout << endl << "NewCustomer: Error accessing Account." <<  115:                 endl;  116:         cout << ex << endl;  117:         return 1;  118:     }  119:   120:     // When this point is reached, the application is finished.  121:     return 0;  122: } 

Notice the additional try and catch statements appearing in lines 85-93 and 99-110 in NewCustomerMain.cpp. Earlier, try and catch were used to catch CORBA::Exceptions, now there are user exceptions to check for as well. The first of these appears with the use of the withdraw() method; note that $250.00 was deposited to the Account, but the program attempts to withdraw $500.00. Expect to see an exception thrown here when the application is run. Also, note that deleteAccount() is called twice for the same Account (once in line 97 and again in line 104). The first call will be successful, but expect the second to result in another exception being thrown because the Account, having already been deleted, no longer exists in the Bank. In the next section, you'll verify that the program results are what you expect.

 


Note:In this example, the first call to deleteAccount() is not contained in a try ... catch block for InvalidAccountException. Although this behavior might be acceptable for this application--because the exception would be handled by the catch (const CORBA::Exception ex) handler--you might consider wrapping the call into its own try ... catch block. The arrangement of these exception handlers is highly dependent on the intent of the application, but there will be few times when you'll want a user exception to be handled by a catchall exception handler such as the CORBA::Exception handler in the previous example. Good exception-handling techniques come with practice and with careful design.

Running the Enhanced Example

Congratulations--you have now successfully added an exception-handling mechanism to the Bank application! All that remains now is to compile and run the application to verify that the results are what you expect. Make sure you run the IDL compiler on all the new and modified IDL source files; also, your IDL compiler might require special command-line arguments to generate code for exception handling. (Incidentally, Visigenic's VisiBroker for C++, version 3.0, does not.) Also, check your C++ compiler's settings to ensure that exception handling is enabled (some compilers don't enable this feature by default).

The order for starting the application components is the same as on Day 6: First start the Naming Service (or osagent, in the case of VisiBroker), followed by the BankServer server, the Bank server, and finally the NewCustomer client application.

Again, starting the BankServer application results in the following output:

BankServer ready.  

On seeing this message, start a Bank server, which produces output similar to the following:

Bank "First Bank" ready.  

Meanwhile, the BankServer will have output this:

BankServerImpl: Registering Bank "First Bank".  

You are now ready to start the NewCustomer application, which produces output similar to the following:

NewCustomer: Creating new Customer:    name: Jeremy Rosenberger    Social Security number: 123456789    address: 123 Main Street    mother's maiden name: Sams  NewCustomer: Connected to Bank "First Bank".  NewCustomer: Opened new Account:    account number: Account00000000    creation date: Oct 14 1997    account balance: 0  NewCustomer: Performing transactions.    Depositing $250.00; new balance is $250    Withdrawing $500.00...; new balance is $  NewCustomer: Exception caught: Insufficient funds    Deleting Account.    Attempting to cause an exception by deleting Account again.  NewCustomer: Exception caught: Invalid Account (as expected)  

Meanwhile, you might notice the following output from the Bank server:

BankImpl: Creating new CheckingAccount for Customer Jeremy Rosenberger.  AccountImpl: Insufficient funds to withdraw specified amount.  BankImpl: Deleting Account "Account00000000".  

Is this the output you expect? Recall that NewCustomer opens a new Account with the information given on the command line, deposits $250 into that account, and then attempts to withdraw $500. Sure enough, NewCustomer reports that the Account had insufficient funds for the withdrawal (thanks to the InsufficientFundsException). Furthermore, you'd expect to see trouble from the attempt to delete the same Account twice. Sure enough, NewCustomer indicates that it tried to delete an invalid Account the second time deleteAccount() was called. In other words, the exception mechanism worked!

Summary

In this chapter, you modified the Bank application to handle exceptions. You started by determining what exceptions might reasonably be raised in various parts of the application, and you modified the IDL interfaces of the application to use those exceptions. At this point, you specified what exceptions could be raised by what methods. You then modified the corresponding C++ header and implementation files for the CORBA server components, specifying the exact conditions under which a particular exception would be thrown. Finally, you wrote a CORBA client application that could handle those exceptions intelligently. The net result is an application that demonstrates a robust design, in the sense that it can deal with exceptional situations--that is, circumstances that should not occur during normal application execution.

 


Note:Although in this book you designed and built a working application before giving a thought to exception handling, typically you will think about exceptions at the same time that you design an application. When deciding which methods belong in an interface, also think about how those methods might be used erroneously, and what mechanisms--in the form of exceptions--might be used to communicate error conditions back to the client. You'll always be able to go back and add exception handling later, but it is wise to at least think about such issues early on in the application development phase.

In the next few chapters you'll continue to modify the Bank application, adding still more functionality. On Day 8, in particular, you will define additional requirements for the system--the capability to support Automated Teller Machines (ATMs). Of course, you'll only be dealing with virtual ATMs rather than real ones, but this will expose you to the development process with a non-trivial CORBA application.

Q&A

Q How do I know whether an abnormal condition is important enough to warrant an exception? In other words, should exceptions be reserved only for fatal error conditions?

A
There are two schools of thought regarding exceptions. One camp suggests that exceptions should be used only to signal a serious error condition--if an exception occurs, the best an application can hope to do is clean up and try to exit gracefully. The alternative approach--and the one that CORBA architecture seems to embrace--is that exceptions might be used to signal just about any abnormal condition. For instance, in the example from this chapter, an account having insufficient funds for a withdrawal is hardly fatal to the application--it simply requires the current transaction to be aborted. This approach can be taken to an extreme, however. Because CORBA exceptions can propagate across networks, they can potentially incur a great amount of overhead compared to native C++ exceptions. As with anything else, use exceptions judiciously.

Q How do I choose exception names?

A
Exception names should be self-describing; that is, an exception name should succinctly describe the specific condition being handled. On the other hand, though, exception names shouldn't be so specific that you find yourself defining multiple exceptions that have almost the same meaning. For instance, InvalidDepositAmountException, InvalidWithdrawalAmountException, InvalidTransferAmountException, InvalidInterestRateAmountException, and so on could reasonably be merged into a single InvalidAmountException, whose name still provides enough information to determine what triggered the exception.

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 exercises in Appendix A.

Quiz

1. What does it mean to raise (or throw) an exception?

2. What does it mean to catch an exception?

3. Why are exceptions useful?

Exercises

1. Modify the following interface definition so that appropriate exceptions are raised in appropriate places.
exception InvalidNumberException { };
exception NoIncomingCallException { };
exception NotOffHookException { };
interface Telephone {
void offHook();
void onHook();
void dialNumber(in string phoneNumber);
void answerCall();

};

 
2. Implement the interface from Exercise 1, raising the appropriate exceptions under the appropriate conditions. (Most of the methods probably won't do anything, except for dialNumber(), which will likely check the validity of the given phone number).

 


Previous chapterNext chapterContents 

 Macmillan Computer Publishing USA

© Copyright, Macmillan Computer Publishing. All rights reserved.

 
原创粉丝点击