Day 9 Using Callbacks to Add Push Capability

<script LANGUAGE="JavaScript"> </script>


Teach Yourself CORBA In 14 Days

Previous chapterNext chapterContents 


Day 9
Using Callbacks to Add Push Capability

 


On Day 8, "Adding Automated Teller Machine (ATM) Capability," you continued development of the sample Bank application by adding Automated Teller Machine (ATM) capabilities. Now, you'll make the final enhancements, this time adding push capability.

 


Note:Although push appears to be one of the latest buzzwords in the industry (particularly where the Internet is concerned), the concept has been around for quite some time. (The Internet is simply one of the more visible applications for it.) Client/server applications typically follow the pull model, in which the client pulls information from the server when it so desires (in other words, the client initiates the transaction). In the push model, the server pushes information to the client--that is, the server initiates the transaction. For example, when a stock price changes, a server can push the updated information to its clients. Push can be much more efficient than pull when information is updated infrequently, especially when there are a number of clients in a system.

Push capability can be utilized in many ways, but in the Bank application you'll use it to implement an account update system. Through this system, customers receive account balance updates every minute. (Of course, this would probably be useless in an actual bank application, but it serves to demonstrate the concept.) The process you'll follow is the same as in the previous chapter:

  • Define additional requirements. Modify the system requirements to specify a requirement for the account update capability.

  • Modify the system design. Translate the additional requirements into changes in system design, again reinforcing the notion of software design as an iterative process.

  • Modify the IDL definitions. Create or update the interface definitions for new classes or classes that have changed since the previous iteration.

  • Implement the new functionality. After the design is finished and then realized in IDL, implement the new functionality and see how the new application works.

Defining Additional Requirements

Recall the system requirements for the Bank application from Day 8. As you did when you added ATM capability to the application, you'll define a set of requirements that describes the desired functionality. For the account update capability, assume the following requirements:

  • Supports the capability for a server to send account balance updates periodically and automatically to customers

  • Supports the capability for customers to request the account update feature

The requirements are straightforward; they formalize the functionality already described. Note that there is only a requirement to request the account update feature; there is no requirement to cancel the feature. Adding such a capability is not difficult, but to keep the sample application simple, this feature is omitted. (Perhaps it would make a good exercise...)

Modifying the Class Diagram

Again, you're ready to translate a modified set of requirements into an updated system design. This time, you won't need to create any additional classes, and you'll need to modify some existing classes only slightly.

Modifying Existing Classes

The modifications required to add account updating to the application are clear-cut. First, a Customer must subscribe to the service, so the Bank must be modified to enable this. The only information required to subscribe to the service is the Account, so the method should take this as a parameter. No other parameters are strictly necessary; the Bank can obtain the Customer(s) from the Account when necessary. Also, no return value is required. The signature for the new method is this:

requestUpdateService(account : Account) : void

The other requirement is to add a method to the Customer class that enables the Bank to send updates when necessary. So that the Customer knows which Account the update is for (Customers can have multiple Accounts, after all), the Account should be a parameter. With just the Account information, the Customer can determine the current balance, but for the sake of convenience, the method will take the balance as a parameter as well. Again, no return value is necessary--and just for fun, make the method oneway as well, so the Bank will be able to send updates without having to wait for responses from the Customers. Here is the signature for the method:

updateAccountBalance(account : Account, balance : float) : void

 


Note:For the updateAccountBalance() operation, using the oneway calling mechanism is a reasonable choice. Recall that the oneway mechanism is unreliable--that is, delivery of oneway messages is not guaranteed. This is acceptable for updateAccountBalance() because the account update messages are not considered critical. In other words, if an occasional update message is not delivered, the impact on the operation of the application is minimal.

Appearing in Figure 9.1 is the modified class diagram for the Bank application, reflecting these additions.

Figure 9.1. The modified Bank application class diagram.

Modifying the IDL Specification

As on Day 8, the modifications to the IDL interface specifications are obvious. Start with the modified Bank interface, appearing in Listing 9.1, with changes highlighted in bold.

Listing 9.1. 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 "ATMCard.idl"

15: #include "Exceptions.idl"

16: 

17: // A Bank provides access to Accounts. It can create an Account

18: // on behalf of a Customer, delete an Account, or list the current

19: // Accounts with the Bank.

20: interface Bank {

21: 

22:     // This Bank's name.

23:     attribute string name;

24: 

25:     // This Bank's address.

26:     attribute string address;

27: 

28:     // Create an Account on behalf of the given Customer, with the

29:     // given account type ("savings" or "checking", where case is

30:     // significant), and the given opening balance.

31:     Account createAccount(in Customer customer, in string

32:             accountType, in float openingBalance);

33: 

34:     // Delete the given Account. If the Account is not with this

35:     // Bank, this operation does nothing.

36:     void deleteAccount(in Account account)

37:             raises (InvalidAccountException);

38: 

39:     // List all Accounts with this Bank.

40:     AccountList getAccounts();

41: 

42:     // Issue an ATMCard with the initial PIN and initially

43:     // authorized on the given Account. If the Account is not with

44:     // this Bank, this operation does nothing.

45:     ATMCard issueATMCard(in short pin, in Account account)

46:             raises (InvalidAccountException);

47: 

48:     // Request the automatic update service on the given Account.

49:     // Account balances will be sent periodically to the Customers

50:     // owning the Account.

51:     void requestUpdateService(in Account account)

52:             raises (InvalidAccountException);

53: };

54: 

55: #endif 

Note that the changes to Bank.idl are minimal, consisting only of the new requestUpdateService() method. Also, notice that the InvalidAccountException comes into play again here, in case an Account is passed that does not belong to the Bank.

The other changes are made to Customer.idl, which appears in Listing 9.2.

Listing 9.2. 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:     // Send an update message to this Customer regarding the given

35:     // Account and its new balance.

36:     oneway void updateAccountBalance(in Account account, in float

37:             balance);

38: };

39: 

40: #endif 

Again, changes to Customer.idl are minimal, adding only the updateAccountBalance() method. Note the use of the oneway modifier, indicating that when this method is called, it will return to the caller immediately.

You're now ready to proceed with the changes to the implementation itself.

Implementing the New Functionality

Implementing the account update functionality is also a simple process. Given that only two methods have been added to the entire system, there are only a couple of steps involved:

  • Implement requestUpdateService() in the BankImpl class. The BankImpl must keep track of all Accounts for which the service is activated; when it comes time to send update messages to the Accounts' Customers, the BankImpl can simply traverse this list of Accounts.

  • Implement updateAccountBalance() in the CustomerImpl class. This method can be as trivial as simply printing a message indicating the Account and its new balance.

Enhancing the BankImpl

First, you'll modify BankImpl to provide the requestUpdateService() functionality. As already mentioned, the BankImpl must maintain a list of Accounts for which the automatic update service is activated. You'll see how this is done in Listing 9.3, BankImpl.h, with changes highlighted in bold.

Listing 9.3. 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:     virtual ATMCard_ptr issueATMCard(CORBA::Short pin, Account_ptr

33:             account) throw (InvalidAccountException);

34:     virtual void requestUpdateService(Account_ptr account) throw

35:             (InvalidAccountException);

36: 

37: protected:

38: 

39:     // Return the next available account number. The result is

40:     // returned in a static buffer.

41:     char* getNextAccountNumber();

42: 

43:     // Return the current date in the form "Mmm DD YYYY". The result

44:     // is returned in a static buffer.

45:     char* getCurrentDate();

46: 

47: private:

48: 

49:     // Default constructor.

50:     BankImpl();

51: 

52:     // This Bank's name.

53:     char* myName;

54: 

55:     // This Bank's address.

56:     char* myAddress;

57: 

58:     // This Bank's Accounts.

59:     std::vector<Account_ptr> myAccounts;

60: 

61:     // The Accounts which are subscribed to the automatic update

62:     // service.

63:     std::vector<Account_ptr> mySubscribedAccounts;

64: 

65:     // The next available account number.

66:     unsigned int myNextAccountNumber;

67: };

68: 

69: #endif 

In BankImpl.h, note the addition of the requestUpdateService() method and the mySubscribedAccounts data member. mySubscribedAccounts is a C++ Standard Template Library vector, just like the myAccounts member, which contains all the Accounts held by a particular Bank. In Listing 9.4, BankImpl.cpp, you'll observe how the elements of mySubscribedAccounts are managed.

Listing 9.4. BankImpl.cpp.
  1: // BankImpl.cpp

  2: 

  3: #include "BankImpl.h"

  4: 

  5: #include <process.h>

  6: 

  7: #include <time.h>

  8: #include <string.h>

  9: #include <iostream.h>

 10: #include <algorithm>

 11: #include <functional>

 12: 

 13: #include "SavingsAccountImpl.h"

 14: #include "CheckingAccountImpl.h"

 15: #include "ATMCardImpl.h"

 16: 

 17: extern CORBA::BOA_var boa;

 18: 

 19: // STL-derived unary function which returns TRUE if Accounts are

 20: // equal.

 21: class IsAccountEqual : public std::unary_function<Account_ptr,

 22:         bool> {

 23: public:

 24:     IsAccountEqual(argument_type account) { myAccount = account; }

 25:     result_type operator()(argument_type account) { return account->

 26:             _is_equivalent(myAccount) != 0; }

 27: private:

 28:     argument_type myAccount;

 29: };

 30: 

 31: void updateAccountThreadFunction(LPVOID pParam) {

 32: 

 33:     std::vector<Account_ptr>* accounts = (std::

 34:             vector<Account_ptr>*)pParam;

 35: 

 36:     while (1) {

 37:         Sleep(60000);

 38:         cout << "BankImpl: Updating Accounts." << endl;

 39: 

 40:         // Iterate through the list of Accounts.

 41:         for (int i = 0; i < accounts->size(); i++) {

 42: 

 43:             // For each Account, get the list of Customers and send

 44:             // an update message to each.

 45:             CustomerList* customers = (*accounts)[i]->

 46:                     getCustomers();

 47:             for (CORBA::Long j = 0; j < customers->length(); j++) {

 48:                 try {

 49:                     (*customers)[i]->

 50:                             updateAccountBalance((*accounts)[i],

 51:                             (*accounts)[i]->balance());

 52:                 } catch (const CORBA::Exception&) {

 53: 

 54:                     // Ignore the exception; we don't care if

 55:                     // there's a problem with the update.

 56:                 }

 57:             }

 58:         }

 59:     }

 60: }

 61: 

 62: // Constructor.

 63: //

 64: // name - This Bank's name.

 65: BankImpl::BankImpl(const char* name) : _sk_Bank(name), myAccounts(),

 66:         mySubscribedAccounts(), myName(strdup(name)),

 67:         myAddress(strdup("123 Elm Street, Anywhere USA 12345")),

 68:         myNextAccountNumber(0) {

 69: 

 70:     _beginthread(&updateAccountThreadFunction, 0,

 71:             &mySubscribedAccounts);

 72: }

 73: 

 74: // Default constructor.

 75: BankImpl::BankImpl() : myAccounts(), mySubscribedAccounts(),

 76:         myName(NULL), myAddress(NULL), myNextAccountNumber(0) {

 77: 

 78: }

 79: 

 80: // Destructor.

 81: BankImpl::~BankImpl() {

 82: 

 83:     cout << "Bank /"" << name() << "/" being destroyed." << endl;

 84:     free(myName);

 85:     free(myAddress);

 86: }

 87: 

 88: char* BankImpl::name() {

 89: 

 90:     return CORBA::strdup(myName);

 91: }

 92: 

 93: void BankImpl::name(const char* val) {

 94: 

 95:     free(myName);

 96:     myName = strdup(val);

 97: }

 98: 

 99: char* BankImpl::address() {

100: 

101:     return CORBA::strdup(myAddress);

102: }

103: 

104: void BankImpl::address(const char* val) {

105: 

106:     free(myAddress);

107:     myAddress = strdup(val);

108: }

109: 

110: Account_ptr BankImpl::createAccount(Customer_ptr customer,

111:         const char* accountType, CORBA::Float openingBalance) {

112: 

113:     Account_ptr newAccount;

114: 

115:     if (strcmp(accountType, "savings") == 0) {

116: 

117:         // Create a new SavingsAccountImpl object for the Account.

118:         cout << "BankImpl: Creating new SavingsAccount for "

119:                 "Customer " << customer->name() << "." << endl;

120:         newAccount = new SavingsAccountImpl(getNextAccountNumber(),

121:                 getCurrentDate(), openingBalance, customer, 10.0);

122:     } else if (strcmp(accountType, "checking") == 0) {

123: 

124:         // Create a new CheckingAccountImpl object for the Account.

125:         cout << "BankImpl: Creating new CheckingAccount for "

126:                 "Customer " << customer->name() << "." << endl;

127:         newAccount = new CheckingAccountImpl(getNextAccountNumber(),

128:                 getCurrentDate(), openingBalance, customer);

129:     } else {

130: 

131:         // Invalid Account type; do nothing.

132:         cout << "BankImpl: Customer " << customer->name() <<

133:                 " requested invalid Account type /"" << accountType

134:                 << "/"." << endl;

135:         return Account::_nil();

136:     }

137: 

138:     // Add the created Account at the end of the list and return it.

139:     ::boa->obj_is_ready(newAccount);

140:     myAccounts.push_back(Account::_duplicate(newAccount));

141:     return newAccount;

142: }

143: 

144: void BankImpl::deleteAccount(Account_ptr account) throw

145:         (InvalidAccountException) {

146: 

147:     std::vector<Account_ptr>::iterator first = myAccounts.begin();

148:     std::vector<Account_ptr>::iterator last = myAccounts.end();

149:     IsAccountEqual predicate(account);

150: 

151:     std::vector<Account_ptr>::iterator matchedAccount = std::

152:             find_if(first, last, predicate);

153:     if (matchedAccount == last) {

154: 

155:         // Invalid Account; throw an exception.

156:         cout << "BankImpl: Attempted to delete invalid Account." <<

157:                 endl;

158:         throw InvalidAccountException();

159:     }

160:     cout << "BankImpl: Deleting Account /"" << account->

161:             accountNumber() << "/"." << endl;

162: 

163:     // Delete the given Account.

164:     myAccounts.erase(matchedAccount);

165:     account->_release();

166: }

167: 

168: AccountList* BankImpl::getAccounts() {

169: 

170:     AccountList* list = new AccountList(myAccounts.size());

171:     CORBA::Long i;

172: 

173:     for (i = 0; i < myAccounts.size(); i++) {

174:         (*list)[i] = Account::_duplicate(myAccounts[i]);

175:     }

176: 

177:     return list;

178: }

179: 

180: ATMCard_ptr BankImpl::issueATMCard(CORBA::Short pin, Account_ptr

181:         account) throw (InvalidAccountException) {

182: 

183:     // First check to see if the Account is with this Bank.

184:     std::vector<Account_ptr>::iterator first = myAccounts.begin();

185:     std::vector<Account_ptr>::iterator last = myAccounts.end();

186:     IsAccountEqual predicate(account);

187: 

188:     std::vector<Account_ptr>::iterator matchedAccount = std::

189:             find_if(first, last, predicate);

190:     if (matchedAccount == last) {

191: 

192:         // Invalid Account; throw an exception.

193:         throw InvalidAccountException();

194:     }

195: 

196:     // If we got this far, the Account must exist with this Bank,

197:     // so we can proceed.

198:     ATMCard_ptr newCard = new ATMCardImpl(pin, account);

199: 

200:     return ATMCard::_duplicate(newCard);

201: }

202: 

203: void BankImpl::requestUpdateService(Account_ptr account) throw

204:         (InvalidAccountException) {

205: 

206:     // First check to see if the Account is with this Bank.

207:     std::vector<Account_ptr>::iterator first = myAccounts.begin();

208:     std::vector<Account_ptr>::iterator last = myAccounts.end();

209:     IsAccountEqual predicate(account);

210: 

211:     std::vector<Account_ptr>::iterator matchedAccount = std::

212:             find_if(first, last, predicate);

213:     if (matchedAccount == last) {

214: 

215:         // Invalid Account; throw an exception.

216:         throw InvalidAccountException();

217:     }

218: 

219:     // If we got this far, the Account must exist with this Bank,

220:     // so we can proceed.

221:     mySubscribedAccounts.push_back(Account::_duplicate(account));

222: }

223: 

224: // Return the next available account number. The result is returned

225: // in a static buffer.

226: char* BankImpl::getNextAccountNumber() {

227: 

228:     static char accountNumber[16] = "Account        ";

229: 

230:     sprintf(accountNumber + 7, "%08u", myNextAccountNumber++);

231: 

232:     return accountNumber;

233: }

234: 

235: // Return the current date in the form "Mmm DD YYYY". The result is

236: // returned in a static buffer.

237: char* BankImpl::getCurrentDate() {

238: 

239:     static char currentDate[12] = "           ";

240: 

241:     time_t ltime;

242:     time(&ltime);

243:     char* ctimeResult = ctime(&ltime);

244: 

245:     memcpy(currentDate, ctimeResult + 4, 3);

246:     memcpy(currentDate + 4, ctimeResult + 8, 2);

247:     memcpy(currentDate + 7, ctimeResult + 20, 4);

248: 

249:     return currentDate;

250: }

 


Warning: BankImpl.cpp, as it appears in Listing 9.4, introduces the use of threads in the server application. Depending on your operating system, however, the file will not compile as listed. BankImpl.cpp makes use of the Win32 APIs for using threads, so it will compile on Windows 95 and NT. Users of other platforms, particularly UNIX platforms, need to modify the code slightly to use the thread API (such as POSIX threads) for their operating system. This is a trivial matter because only one new thread is created in the BankImpl.cpp implementation.

Also, as a reminder, the code presented here is not thread-safe--for the sake of clarity, no checks are made to ensure that both threads don't access the mySubscribedAccounts vector simultaneously. This non-thread-safe code works for demonstration purposes, but for a production system, you'll definitely want to ensure that all code is thread-safe when using multithreading in an application.

Now take a closer look at Listing 9.4. The first thing you'll notice, in lines 31-60, is the addition of a function called updateAccountThreadFunction() that executes in a second thread.

First of all, as you can see in lines 31-34, updateAccountThreadFunction() expects its argument to be a pointer to an STL vector of Accounts (you'll see later that this is the argument with which the function is actually called).

What is happening in lines 36-37 is that the thread is being set up to run for as long as the server application is running (hence, the while (1), which will never exit). Also, the loop is set up to sleep for 60,000 milliseconds (one minute) between executions.

Every minute, the for statement in line 41 will cause the thread to iterate through its list of Accounts (lines 43-46), and then to iterate through each of the Customers belonging to those Accounts, as you can see in lines 47-51. Also, in line 51 you see that the updateAccountBalance() message is sent to each of the Customers.

Finally, if for some reason an exception is thrown by the remote method call, it is ignored, as you can see in lines 52-56. (updateAccountThreadFunction() catches the exception but does nothing with it.)

Enhancing the CustomerImpl

The enhancements to CustomerImpl are simple. CustomerImpl need only accept the updateAccountBalance() message and print a message indicating the new Account balance. The modified CustomerImpl.h and CustomerImpl.cpp appear in Listings 9.5 and 9.6.

Listing 9.5. CustomerImpl.h.
 1: // CustomerImpl.h

 2: 

 3: #ifndef CustomerImpl_h

 4: #define CustomerImpl_h

 5: 

 6: #include "../Customer_s.h"

 7: #include "../ATMCard_c.h"

 8: 

 9: class CustomerImpl : public _sk_Customer {

10: 

11: public:

12: 

13:     // Constructor.

14:     //

15:     // name - Customer's name.

16:     // socialSecurityNumber - Customer's Social Security number.

17:     // address - Customer's address.

18:     // mothersMaidenName - Customer's mother's maiden name.

19:     CustomerImpl(const char* name, const char* socialSecurityNumber,

20:             const char* address, const char* mothersMaidenName);

21: 

22:     // Destructor.

23:     ~CustomerImpl();

24: 

25:     // These methods are described in Customer.idl.

26:     virtual char* name();

27:     virtual void name(const char* val);

28:     virtual char* socialSecurityNumber();

29:     virtual char* address();

30:     virtual void address(const char* val);

31:     virtual char* mothersMaidenName();

32:     virtual AccountList* getAccounts();

33:     virtual void updateAccountBalance(Account_ptr account, CORBA::

34:             Float balance);

35: 

36: private:

37: 

38:     // Default constructor.

39:     CustomerImpl();

40: 

41:     // This Customer's name.

42:     char* myName;

43: 

44:     // This Customer's Social Security number.

45:     char* mySocialSecurityNumber;

46: 

47:     // This Customer's address.

48:     char* myAddress;

49: 

50:     // This Customer's mother's maiden name.

51:     char* myMothersMaidenName;

52: 

53:     // This Customer's Accounts.

54:     AccountList myAccounts;

55: 

56:     // This Customer's ATMCards.

57:     ATMCardList myATMCards;

58: };

59: 

60: #endif
Listing 9.6. CustomerImpl.cpp.
 1: // CustomerImpl.cpp

 2: 

 3: #include "CustomerImpl.h"

 4: 

 5: #include <iostream.h>

 6: #include <string.h>

 7: 

 8: // Constructor.

 9: //

10: // name - Customer's name.

11: // socialSecurityNumber - Customer's Social Security number.

12: // address - Customer's address.

13: // mothersMaidenName - Customer's mother's maiden name.

14: CustomerImpl::CustomerImpl(const char* name, const char*

15:         socialSecurityNumber, const char* address, const char*

16:         mothersMaidenName) : _sk_Customer(socialSecurityNumber),

17:         myName(strdup(name)),

18:         mySocialSecurityNumber(strdup(socialSecurityNumber)),

19:         myAddress(strdup(address)),

20:         myMothersMaidenName(strdup(mothersMaidenName)),

21:         myAccounts(), myATMCards() {

22: 

23: }

24: 

25: // Default constructor.

26: CustomerImpl::CustomerImpl() : myName(NULL),

27:         mySocialSecurityNumber(NULL), myAddress(NULL),

28:         myMothersMaidenName(NULL), myAccounts(), myATMCards() {

29: 

30: }

31: 

32: // Destructor.

33: CustomerImpl::~CustomerImpl() {

34: 

35:     free(myName);

36:     free(mySocialSecurityNumber);

37:     free(myAddress);

38:     free(myMothersMaidenName);

39: }

40: 

41: char* CustomerImpl::name() {

42: 

43:     return CORBA::strdup(myName);

44: }

45: 

46: void CustomerImpl::name(const char* val) {

47: 

48:     free(myName);

49:     myName = strdup(val);

50: }

51: 

52: char* CustomerImpl::socialSecurityNumber() {

53: 

54:     return CORBA::strdup(mySocialSecurityNumber);

55: }

56: 

57: char* CustomerImpl::address() {

58: 

59:     return CORBA::strdup(myAddress);

60: }

61: 

62: void CustomerImpl::address(const char* val) {

63: 

64:     free(myAddress);

65:     myAddress = strdup(val);

66: }

67: 

68: char* CustomerImpl::mothersMaidenName() {

69: 

70:     return CORBA::strdup(myMothersMaidenName);

71: }

72: 

73: AccountList* CustomerImpl::getAccounts() {

74: 

75:     return &myAccounts;

76: }

77: 

78: void CustomerImpl::updateAccountBalance(Account_ptr account,

79:         CORBA::Float balance) {

80: 

81:     cout << "CustomerImpl: Received account update:" << endl <<

82:             "  New balance is ___FCKpd___7quot; << balance << endl;

83: }

Enhancing the ATMClient

The modifications to ATMClientMain.cpp are easy to follow (see Listing 9.7). The only additions are that the ATMClient now requests the account update service from the Bank when the Account is created, and when the ATMClient is finished, it waits for two minutes to give the Bank a chance to call updateAccountBalance() once or twice before the ATMClient exits. (Like BankImpl, ATMClient uses the Win32 API to cause the current thread to sleep; again, non-Windows developers need to substitute the appropriate method call here.)

Listing 9.7. ATMClientMain.cpp.
  1: // ATMClientMain.cpp

  2: 

  3: #include <iostream.h>

  4: #include <stdlib.h>

  5: 

  6: #include "../Customer/CustomerImpl.h"

  7: 

  8: #include "../Bank_c.h"

  9: #include "../BankServer_c.h"

 10: #include "../ATM_c.h"

 11: 

 12: int main(int argc, char *const *argv) {

 13: 

 14:     // Check the number of arguments; there should be exactly five

 15:     // (six counting the executable name itself).

 16:     if (argc != 6) {

 17:         cout << "Usage: ATMClient <name> <social security number>"

 18:                 " <address> <mother's maiden name> <PIN>" << endl;

 19:         return 1;

 20:     }

 21: 

 22:     // Assign the command line arguments to the Customer attributes.

 23:     const char* name = argv[1];

 24:     const char* socialSecurityNumber = argv[2];

 25:     const char* address = argv[3];

 26:     const char* mothersMaidenName = argv[4];

 27:     CORBA::Short pin = atoi(argv[5]);

 28: 

 29:     // Initialize the ORB and BOA.

 30:     CORBA::ORB_var orb = CORBA::ORB_init(argc, argv);

 31:     CORBA::BOA_var boa = orb->BOA_init(argc, argv);

 32: 

 33:     // Create a Customer object.

 34:     cout << "ATMClient: Creating new Customer:" << endl;

 35:     cout << "  name: " << name << endl;

 36:     cout << "  Social Security number: " << socialSecurityNumber <<

 37:             endl;

 38:     cout << "  address: " << address << endl;

 39:     cout << "  mother's maiden name: " << mothersMaidenName << endl;

 40:     CustomerImpl customer(name, socialSecurityNumber, address,

 41:             mothersMaidenName);

 42: 

 43:     // Notify the BOA that the CustomerImpl object is ready.

 44:     boa->obj_is_ready(&customer);

 45: 

 46:     // Locate a BankServer object and try to get a list of Banks

 47:     // and ATMs from it.

 48:     BankServer_var bankServer;

 49:     try {

 50:         bankServer = BankServer::_bind();

 51:     } catch (const CORBA::Exception& ex) {

 52: 

 53:         // The bind attempt failed...

 54:         cout << "ATMClient: Unable to bind to a BankServer:" <<

 55:                 endl;

 56:         cout << ex << endl;

 57:         return 1;

 58:     }

 59:     cout << "ATMClient: Successfully bound to a BankServer." <<

 60:             endl;

 61: 

 62:     BankList_ptr banks;

 63:     ATMList_ptr ATMs;

 64: 

 65:     try {

 66:         banks = bankServer->getBanks();

 67:         ATMs = bankServer->getATMs();

 68:     } catch (const CORBA::Exception& ex) {

 69: 

 70:         // The attempt failed...

 71:         cout << "ATMClient: Unable to get lists of Banks and ATMs:"

 72:                 << endl;

 73:         cout << ex << endl;

 74:         return 1;

 75:     }

 76: 

 77:     // Use the first Bank and the first ATM that appear in the

 78:     // lists.

 79:     if (banks->length() == 0) {

 80: 

 81:         // No Banks were available.

 82:         cout << "ATMClient: No Banks available." << endl;

 83:         return 1;

 84:     }

 85:     if (ATMs->length() == 0) {

 86: 

 87:         // No ATMs were available.

 88:         cout << "ATMClient: No ATMs available." << endl;

 89:         return 1;

 90:     }

 91:     Bank_var bank = (*banks)[0];

 92:     ATM_var atm = (*ATMs)[0];

 93:     cout << "ATMClient: Using Bank /"" << bank->name() << "/" and"

 94:             << " ATM /"" << atm->name() << "/"." << endl;

 95: 

 96:     // Do some cool stuff.

 97: 

 98:     Account_var account;

 99:     ATMCard_var atmCard;

100: 

101:     try {

102:         account = bank->createAccount(&customer, "checking", 0.0);

103:     } catch (const CORBA::Exception& ex) {

104: 

105:         // The createAccount() attempt failed...

106:         cout << "ATMClient: Unable to create Account." << endl;

107:         cout << ex << endl;

108:         return 1;

109:     }

110: 

111:     try {

112: 

113:         // Request the automatic account update service from the

114:         // Bank.

115:         bank->requestUpdateService(account);

116:     } catch (const CORBA::Exception& ex) {

117: 

118:         // The requestUpdateService() attempt failed...

119:         cout << "ATMClient: Unable to create Account." << endl;

120:         cout << ex << endl;

121:         return 1;

122:     }

123: 

124:     try {

125: 

126:         // Print out some Account statistics.

127:         cout << "ATMClient: Opened new Account:" << endl;

128:         cout << "  account number: " << account->accountNumber() <<

129:                 endl;

130:         cout << "  creation date: " << account->creationDate() <<

131:                 endl;

132:         cout << "  account balance: " << account->balance() << endl;

133: 

134:         // Ask the Bank to issue an ATMCard for the newly-created

135:         // Account.

136:         cout << "ATMClient: Getting ATMCard from Bank." << endl;

137:         try {

138:             atmCard = bank->issueATMCard(pin, account);

139:         } catch (const InvalidAccountException&) {

140: 

141:             // For some reason, the Account was invalid (this

142:             // shouldn't happen).

143:             cout << "ATMClient: Exception caught: Invalid Account"

144:                     << endl;

145:             return 1;

146:         }

147: 

148:         // Perform some transactions on the Account through the

149:         // ATM.

150:         cout << "ATMClient: Performing transactions." << endl;

151:         try {

152:             cout << "  Depositing $250.00..." << endl;

153:             cout << "  New balance is ___FCKpd___8quot; << atm->deposit(atmCard,

154:                     account, pin, 250.00) << endl;

155: 

156:             // This will throw an exception since we're trying to

157:             // withdraw too much.

158:             cout << "  Withdrawing $500.00..." << endl;

159:             cout << "  New balance is ___FCKpd___8quot; << atm->withdraw(atmCard,

160:                     account, pin, 500.00) << endl;

161:         } catch (AuthorizationException&) {

162:             cout << "ATMClient: Exception caught: Invalid PIN or "

163:                     << "No authorization (as expected)" << endl;

164:         } catch (InvalidAmountException&) {

165:             cout << "ATMClient: Exception caught: Invalid amount"

166:                     << endl;

167:         } catch (InsufficientFundsException&) {

168:             cout << "ATMClient: Exception caught: Insufficient " <<

169:                     "funds" << endl;

170:         }

171: 

172:         // Perform some more transactions on the Account through

173:         // the ATM.

174:         cout << "ATMClient: Performing more transactions." << endl;

175:         try {

176:             cout << "  Depositing $500.00..." << endl;

177:             cout << "  New balance is ___FCKpd___8quot; <<

178:                     atm->deposit(atmCard, account, pin, 500.00) <<

179:                     endl;

180: 

181:             // This will throw an exception since we're using the

182:             // wrong PIN.

183:             cout << "  Withdrawing $250.00 with incorrect PIN..."

184:                     << endl;

185:             cout << "  New balance is ___FCKpd___8quot; << atm->withdraw(atmCard,

186:                     account, pin + 1, 250.00) << endl;

187:         } catch (AuthorizationException&) {

188:             cout << "ATMClient: Exception caught: Invalid PIN or "

189:                     << "No authorization (as expected)" << endl;

190:         } catch (InvalidAmountException&) {

191:             cout << "ATMClient: Exception caught: Invalid amount"

192:                     << endl;

193:         } catch (InsufficientFundsException&) {

194:             cout << "ATMClient: Exception caught: Insufficient " <<

195:                     "funds" << endl;

196:         }

197: 

198:         // Get rid of the Account.

199:         try {

200:             cout << "  Deleting Account." << endl;

201:             bank->deleteAccount(account);

202: 

203:             // Attempt to delete the Account again, just for kicks.

204:             // This should result in an exception being thrown.

205:             cout << "  Attempting to cause an exception by " <<

206:                     "deleting Account again." << endl;

207:             bank->deleteAccount(account);

208:         } catch (const InvalidAccountException&) {

209: 

210:             // Sure enough, the exception was thrown.

211:             cout << "ATMClient: Exception caught: Invalid " <<

212:                     "Account (as expected)" << endl;

213:         }

214:     } catch (const CORBA::Exception& ex) {

215: 

216:         // Some operation on the Account failed...

217:         cout << "ATMClient: Error accessing Account:" << endl;

218:         cout << ex << endl;

219:         return 1;

220:     }

221: 

222:     // Sleep for long enough to catch an Account update message or

223:     // two.

224:     Sleep(120000);

225: 

226:     // When this point is reached, the application is finished.

227:     return 0;

228: }

Running the Application

Once again, you're ready to run the modified application. The process is exactly the same as in the previous chapter, but the output from the various applications will be slightly different, as you would expect. Again, start by running the BankServer application:

BankServer

Again, the output of the BankServer will be this:

BankServer ready.

You're now ready to start the Bank application

Bank "First Bank"

which, again, will output this:

Bank "First Bank" ready.

Meanwhile, the BankServer will output this:

BankServerImpl: Registering Bank "First Bank".

Now you'll start the ATM application:

ATM "First Bank ATM"

The ATM application will display the following:

ATM "First Bank ATM" ready.

The BankServer, again, will output the message:

BankServerImpl: Registering ATM "First Bank ATM".

Finally, you're ready to run the ATMClient application. You can do so by typing the following:

ATMClient "Jeremy Rosenberger" 123456789 "123 Main Street" Doe 1234

The ATMClient will again display the following:

ATMClient: Creating new Customer:

  name: Jeremy Rosenberger

  Social Security number: 123456789

  address: 123 Main Street

  mother's maiden name: Doe

ATMClient: Successfully bound to a BankServer.

ATMClient: Using Bank "First Bank" and ATM "First Bank ATM".

ATMClient: Opened new Account:

  account number: Account00000000

  creation date: Oct 20 1997

  account balance: 0

ATMClient: Getting ATMCard from Bank.

ATMClient: Performing transactions.

  Depositing $250.00...

  New balance is $250

  Withdrawing $500.00...

ATMClient: Exception caught: Insufficient funds

ATMClient: Performing more transactions.

  Depositing $500.00...

  New balance is $750

  Withdrawing $250.00 with incorrect PIN...

ATMClient: Exception caught: Invalid PIN or No authorization (as expected)

  Deleting Account.

  Attempting to cause an exception by deleting Account again.

ATMClient: Exception caught: Invalid Account (as expected)

At this point, the ATMClient will sleep for two minutes while waiting for messages from the Bank. Be patient, and the ATMClient will eventually output

CustomerImpl: Received account update:

  New balance is $750

All this will go by very quickly, but after it's all over, you can go to the other application windows and see some evidence of what transpired here. Looking first at the BankServer application, you'll see this (with new output messages highlighted in bold):

BankServer ready.

BankServerImpl: Returning list of 1 Banks.

BankServerImpl: Returning list of 1 ATMs.

The output of the other applications will be the same as last time, except for the Bank application. Turn your attention to the window in which the Bank is running and you will see the following, familiar output:

Bank "First Bank" ready.

BankImpl: Creating new CheckingAccount for Customer Jeremy Rosenberger.

AccountImpl: Insufficient funds to withdraw specified amount.

BankImpl: Deleting Account "Account00000000".

BankImpl: Attempted to delete invalid Account.

Stay tuned for a few moments, and you will see the following (if it took you a minute or so to bring the Bank output window up, this might already be on your screen):

BankImpl: Updating Accounts.

Recall that this message is output just before the second thread in the Bank application sends the update messages to all the Account owners.

Ideas for Future Enhancements

You've only begun to scratch the surface of what can be done with CORBA. As far as the Bank application is concerned, there are a number of possible enhancements. As you progress into advanced CORBA topics in the upcoming days, you'll make a few more enhancements to the Bank application, but the possibilities for enhancements are limitless. If you want to further experiment with the Bank application, here are a few ideas:

  • SavingsAccounts already support an interest rate, but the interest is never added to the account balances. Implement a mechanism--probably using a separate thread in the Bank application--that periodically adds interest to each SavingsAccount.

  • CheckingAccounts typically feature overdraft protection; that is, withdrawing a greater amount than is available in the account automatically dips into the customer's line of credit.

  • As mentioned numerous times before, the sample code presented here is not thread-safe. Modify the code so that it is thread-safe.

  • Add a new type of account--perhaps a mutual fund or stock market account. As an added feature, make it possible for customers to subscribe to a service that will automatically inform them--using push messaging--of the account's performance.

Again, the possibilities for enhancements are endless. Adding features or robustness to the Bank application on your own will help you to hone your skills for developing CORBA applications.

Summary

Today you added a simple capability to the Bank application--for the Bank server to push updated information to the bank customers. Although the implementation for this capability is simple, the potential of the push architecture is very great. Indeed, as this book is being written, a number of companies are vying to create the de facto standard for pushing content to users on the Internet.

You were also reminded--albeit briefly--of the importance of writing thread-safe code in a multithreaded environment. Although this book takes a "do as I say, not as I do" approach to writing thread-safe code, it is very important that when writing multithreaded applications, you take care to ensure thread safety. In a sample application with only one user, thread safety is not likely to be an issue because chances are small that two threads will use the same data at the same time. However, in a production system--particularly an enterprisewide system--the penalty for writing non-thread-safe code can be stiff, usually resulting in the corruption of data.

On Day 10 you'll shift gears into some more advanced CORBA topics--a number of design issues that are involved with CORBA, along with a few suggestions about how to deal with those issues. You'll get a small break from further developing the Bank application, but the example does return in future chapters when you study additional advanced CORBA topics, such as the use of the Dynamic Invocation Interface (DII), CORBAservices and CORBAfacilities, and using Java with CORBA.

Q&A

Q What's the big deal about push technology anyway?

A
Properly implemented, a push architecture can save users the trouble of actively searching for desired information. (However, if the application goes overboard with the information pushed to the user, the user might suffer from the new problem of information overload.) Also, push technology has the potential to conserve system resources. Information can be delivered to users as it is updated (as opposed to requiring users to periodically check for updated data, which can be inefficient if the data doesn't change very often).

Q It seems to me that push method calls can almost always be oneway calls. Is this accurate?

A
To the extent that the pushed information is not considered essential for the purposes of the application, this is true. Using oneway calls allows for more efficient server implementations (because the server does not have to wait for a reply from the clients), at the expense of reliability of message delivery. In the case of a server that delivers account balance updates or stock quote updates to casual subscribers, it usually doesn't matter if an occasional update message is lost. When the information being updated is considered essential, oneway is usually not a good choice.

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. Why does the issue of thread safety become important in the sample application developed in this chapter?

2
. Instead of using oneway methods to notify clients of updates, can you think of another way to efficiently send update messages to clients? ( Hint: Multithreading could come in handy here.)

Exercises

1. It was noted earlier in the chapter that no facility currently exists to cancel the automatic account update service. Provide an IDL method signature for such an operation. Don't forget to include appropriate exceptions, if any.

2
. Implement the account update cancellation method from Exercise 1.

 


Previous chapter Next chapter Contents

Macmillan Computer Publishing USA

 

© Copyright, Macmillan Computer Publishing. All rights reserved.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值