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:

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:

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:

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 $" << 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 $" << 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 $" << 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 $" <<
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 $" << 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:

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 chapterNext chapterContents


Macmillan Computer Publishing USA

© Copyright, Macmillan Computer Publishing. All rights reserved.