The main purpose of this code example is to highlight and explain how to write application logic using the C++ framework generated by the C++ Expert.
This section describes the following parts of the sample application:
The sample application files installed with the C++ Expert are fully annotated and explain the actions of each section of code. The files containing the source code for this walk-through can be found in the NoDB (no database) sample for the C++ Expert. The NoDB sample uses an in-memory database instead of accessing an external database. By default, these files can be found at In the Two types of comments appear throughout the sample application code:
YourDrive
:TUXBldr\Samples\CPPExpert\NoDB
(Windows NT)<
Builder_Install_dir
>/client/Samples/CPPExpert/NoDB
(UNIX)
Samples\CPPExpert
directory, you will also find a directory called Ora
. This sample uses Oracle 7.3.3 as its database.
'---
pertain to the application in general.
Keep in mind that the sample application code is presented as an example of how you can use the C++ Expert API and generated framework, and does not necessarily imply application design recommendations. There are many ways to leverage the sample code to fit your own design techniques, preferences, and the requirements of your particular application.
The following sections show how the Open Account service is implemented and used:
Implementing a Service
The
Warning:
Any code that you add to the files generated by the C++ Expert must be placed between the protected section markers. This prevents subsequent generation of sample application code from overwriting your additions. Each section name is unique and should not be altered.
BackOfficeImpl.cpp
file is generated by the C++ Expert. The business logic for the Open Account service is implemented within this file within the protected code section.
Listing 5-2
Generated Signature of the Service Member Function
BANKAPP::BACKOFFICE::OPEN_ACCTReply*
BACKOFFICEImpl::OPEN_ACCT(long BRANCH_ID,
const char* SAMOUNT, char ACCT_TYPE, const char* LAST_NAME,
const char* FIRST_NAME, char MID_INIT, const char* SSN,
const char* ADDRESS, const char* PHONE, char OPEN_CR)
throw (BEATuxBldr::TuxException*, OPEN_ACCTException*)
{
BANKAPP::BACKOFFICE::OPEN_ACCTReply* pReply;
// $[BEGIN_PROTECTED_SECTION]:BANKAPP_BACKOFFICE_OPEN_ACCT_5
...
// $[END_PROTECTED_SECTION]
return pReply;
}
The following code declares some variables and output parameters used in the OPEN_ACCT
service.
Listing 5-3 Declare Variables and Output Parameters
char *STATLIN;
long lNewAccountId;
float fAmount;
char SBALANCE[10];
This code transforms the format of some input parameters for use with the Account
and OPEN_ACCTReply
object.
Listing 5-4 Transform Input Parameters for OPEN_ACCT
sscanf(SAMOUNT,"%f",&fAmount);
sprintf(SBALANCE,"$%.2f",fAmount);
The Account
and AccountFactory
objects are used to simulate a persistent storage mechanism, since no database is used with this sample. These objects are not generated by the C++ Expert.
Account.cpp
file contains the implementation of the Account
class. This class represents a database row.
This code creates an Account
object using input parameters we declared as well as the input parameters found in signature of the service (see Listing 5-2). This object is then added to the in-memory database using the AccountFactory
.
Listing 5-5
Add a New Account to the In-memory Database
Account * pAccount = new Account
( 0, BRANCH_ID, SSN, fAmount,
ACCT_TYPE, LAST_NAME, FIRST_NAME,
MID_INIT, PHONE,
ADDRESS );
long stat = AccountFactory::Instance()->createAccount(
pAccount,
&lNewAccountId );
If the createAccount
member function invocation returns an error, we create and throw the OPEN_ACCTException exception with a description of the error. This exception will be caught by the client that makes the call to the OPEN_ACCT service.
Listing 5-6 Check for Application Errors
if ( stat == AccountFactory::AccountDBfull ) {
STATLIN = "Account data base is full";
throw new OPEN_ACCTException(STATLIN);
} else {
STATLIN = "Success";
}
If createAccount
does not return an error, we create a reply object (OPEN_ACCTReply
) containing the output data to be returned to the client and return.
Listing 5-7 Send the Reply to the Client
pReply = new BANKAPP::BACKOFFICE::OPEN_ACCTReply(lNewAccountId,
SBALANCE, STATLIN);
return pReply;
The Do_OPEN_ACCT
function in the file BankAppClient.cpp
provides an example of how to make synchronous requests using the generated framework. This function performs synchronous calls to the OPEN_ACCT
service.
The following sections show how a synchronous call to the Open Account service is implemented and used:
We create the BACKOFFICE service request object, which contains the
Create the Service Request Object
OPEN_ACCT
and CLOSE_ACCT
member functions that provide access to the corresponding services. In this example, we focus on the OPEN_ACCT
service. The CLOSE_ACCT
service is implemented in a similar fashion in the BankAppClient.cpp
file.
Listing 5-8
Create Service Request Object
BACKOFFICE *pBACKOFFICE;
pBACKOFFICE = new BACKOFFICE();
The following code declares parameters used for calling the OPEN_ACCT
service.
Listing 5-9 Declare Parameters
long ACCOUNT_ID;
char SAMOUNT[12];
char SBALANCE[12];
long BRANCH_ID;
char ACCT_TYPE;
char LAST_NAME[21];
char FIRST_NAME[21];
char MID_INIT;
char SSN[12];
char ADDRESS[61];
char PHONE[13];
char OPEN_CR;
Next, we prompt the user for information about the function they requested. The values are assigned to the input parameters in preparation for our synchronous call to the OPEN_ACCT
service in the BANKAPP server.
Listing 5-10 Prompt for the Input Parameters for OPEN_ACCT
cout << "\n--- OPEN_ACCT operation" << endl;
cout << "\nEnter BRANCH_ID(nn): " << endl;
gets (szInputBuffer);
BRANCH_ID = (long)atol(szInputBuffer);
cout <<"Enter SSN(nnn-nn-nnnn): " << endl;
gets (SSN);
cout <<"Enter SAMOUNT(nnnnn.nn): " << endl;
gets (SAMOUNT);
cout <<"Enter ACCT_TYPE('C'/'S'): " << endl;
gets (szInputBuffer);
ACCT_TYPE = *szInputBuffer;
cout <<"Enter LAST_NAME(20 chars): " << endl;
gets (LAST_NAME);
cout <<"Enter FIRST_NAME(20 chars): " << endl;
gets (FIRST_NAME);
cout <<"Enter MID_INIT(1 char): " << endl;
gets (szInputBuffer);
MID_INIT = *szInputBuffer;
cout <<"Enter PHONE(nnn-nnn-nnnn): " << endl;
gets (PHONE);
cout <<"Enter ADDRESS(60 chars): " << endl;
gets (ADDRESS);
OPEN_CR = 'A';
We now open an account by making a call to the OPEN_ACCT
service. We do not explicitly set the mode flag on the service request object because this object uses the synchronous mode of communication by default. (For more information about communications mode settings, see InvocationType
in the API.)
The OPEN_ACCT
member function returns a reply object (pOPEN_ACCTReply) containing the results of the service call.
When the call to the OPEN_ACCT
service is made, we must check for exceptions by looking at values in TuxException
and the generated exception (OPEN_ACCTException
) associated with the service. The TuxException
exception is thrown when the generated implementation code has detected a BEA TUXEDO system error. The generated exception is thrown when any application error occurs. For more information about how generated exceptions are named and used, see the Generated Exception section in the API.
Listing 5-11 Make a Synchronous Call to the OPEN_ACCT Service
cout <<"Calling OPEN_ACCT..." << endl;
try {
pOPEN_ACCTReply = pBACKOFFICE->OPEN_ACCT(
BRANCH_ID,
SAMOUNT,
ACCT_TYPE,
LAST_NAME,
FIRST_NAME,
MID_INIT,
SSN,
ADDRESS,
PHONE,
OPEN_CR);
} catch (BANKAPP::BACKOFFICE::OPEN_ACCTException *e) {
cout << "\n--- OPEN_ACCTException:" << endl;
cout << "\n--- STATLIN=" << e->_STATLIN << endl;
delete e;
return;
} catch (BEATuxBldr::TuxException *te) {
cout << "\n--- TuxException=" << te->GetMessage() << endl;
delete te;
return;
}
When the service call returns, we invoke the accessor functions of the pOPEN_ACCTReply
object to determine the results. For each output parameter there is a specific function generated that returns the value of that output parameter. For example, the GetSBALANCE
member function is used to get the value of the SBALANCE output parameter from the OPEN_ACCT
service. For more information about reply objects, see the Generated Service Reply section in the API. For more information about the pOPEN_ACCTReply
object, see the Make a Synchronous Call to the OPEN_ACCT Service section.
Listing 5-12 Display the Results of the Service Replies
cout << "\nOutput ACCOUNT_ID: " <<
pOPEN_ACCTReply->GetACCOUNT_ID() << endl;
cout << "\nOutput SBALANCE: " <<
pOPEN_ACCTReply->GetSBALANCE() << endl;
cout << "\nOutput STATLIN: " <<
pOPEN_ACCTReply->GetSTATLIN() << endl;
The Do_Transfer
function in the file BankAppClient.cpp
provides an example of how to use asynchronous communication and transaction demarcation with the generated framework. This function performs asynchronous calls to the DEPOSIT
and WITHDRAWAL
services within the scope of a transaction to perform a transfer of an amount from one account to another.
The following sections show how asynchronous calls to the WITHDRAWAL
and DEPOSIT
services within a transaction are implemented and used:
We create the We also create the TELLER service request object, which contains the
Create the Transaction and Service Request Objects
Transaction
object once at the beginning of the application with global scope so the Transaction
is available to all modules from a single reference point.
WITHDRAWAL
and DEPOSIT
member functions. We will use these member functions to implement the TRANSFER
function of the BANKAPP application.
Listing 5-13
Create the Transaction and Service Request Objects
TELLER *pTELLER;
BEATuxBldr::Transaction *pTrans;
pTELLER = new TELLER();
pTrans = new BEATuxBldr::Transaction();
Since the Do_Transfer
function uses both the WITHDRAWAL
and DEPOSIT
services, we need to ensure that parameters are declared for those services. We also declare the reply object pointers for calls to WITHDRAWAL
and DEPOSIT
services.
Listing 5-14 Declare Parameters and Variables
long To_ACCOUNT_ID;
long From_ACCOUNT_ID;
char SAMOUNT[12];
BANKAPP::TELLER::WITHDRAWALReply* pWITHDRAWALReply;
BANKAPP::TELLER::DEPOSITReply* pDEPOSITReply;
After a transaction is started, all subsequent service calls participate in that transaction until it is rolled back or committed. Calls made to the WITHDRAWAL
and the DEPOSIT
services are contained within a transaction to ensure database consistency. We start a transaction by calling the Do_BeginTransaction
wrapper function to perform transaction demarcation, hide the exception handling and keep the main code cleaner.
In the sample code we have wrapped the call that begins the transaction within a function called Do_BeginTransaction
. The NoTransaction
and SubtransactionsUnavailable
exceptions should be handled by any application that uses the Transaction::begin
member function. The Do_BeginTransaction function allows the application to consistently handle exceptions that may be thrown by a Transaction
.
Listing 5-15 Start a Transaction
int Do_BeginTransaction()
{
try {
pTrans->begin();
} catch (BEATuxBldr::NoTransaction *ne) {
cout << "\n--- Begin trans err; NoTransaction=" <<
ne->GetMessage() << endl;
delete ne;
return FALSE;
} catch (BEATuxBldr::SubTransactionsUnavailable *se) {
cout << "\n--- Begin trans err;
SubTransactionsUnavailable=" <<
se->GetMessage() << endl;
delete se;
return FALSE;
}
return TRUE;
}
We now prompt the user for input and assign returned values to parameters in preparation for our asynchronous call to the WITHDRAWAL
and DEPOSIT
service in the BANKAPP server.
Listing 5-16 Prompt for Input Parameters for WITHDRAWAL and DEPOSIT
cout << "\n--- TRANSFER operation" << endl;
cout << "\nEnter From ACCOUNT_ID(nnnnnn): " << endl;
gets (szInputBuffer);
From_ACCOUNT_ID = (long)atol(szInputBuffer);
cout <<"Enter To ACCOUNT_ID(nnnnnn): " << endl;
gets (szInputBuffer);
To_ACCOUNT_ID = (long)atol(szInputBuffer);
cout <<"Enter SAMOUNT(nnnnn.nn): " << endl;
gets (SAMOUNT);
The TRANSFER
transaction will be performed by making calls to the WITHDRAWAL
service and the DEPOSIT
service, in that order. These calls are made asynchronously. We must explicitly set the mode flag to ASYNCHRONOUS on the service request object to specify this mode each time. (For more information about communications mode settings, see InvocationType
.) The calls made to the WITHDRAWAL
and DEPOSIT
services are contained within a transaction to ensure database consistency.
When the calls to WITHDRAWAL
and DEPOSIT return, we must check for exceptions by looking at values contained in TuxException
. This exception is thrown when the generated implementation code has detected a BEA TUXEDO system error. If any exceptions occur, we must rollback any active transaction. We handle this exception with in-line code in the sample application instead of calling another function.
We must also check for exceptions on the rollback. To keep the code more readable, the exception processing on the rollback is placed in a separate function (see the Rolling back a Transaction section).
Listing 5-17 Make Asynchronous Calls to the WITHDRAWAL and DEPOSIT Services
if (!Do_BeginTransaction()) return;
cout <<"Calling WITHDRAWAL..." << endl;
pTELLER->SetInvoke(BEATuxBldr::ASYNCHRONOUS);
try {
pWITHDRAWALReply = pTELLER->WITHDRAWAL(
From_ACCOUNT_ID,
SAMOUNT);
} catch (BEATuxBldr::TuxException *te) {
cout << "\n--- Withdrawal TuxException=" <<
te->GetMessage() << endl;
delete te;
//*** Rollback active transaction
Do_RollbackTransaction();
return;
}
pTELLER->SetInvoke(BEATuxBldr::ASYNCHRONOUS);
cout << "\nCalling DEPOSIT..." << endl;
try {
pDEPOSITReply = pTELLER->DEPOSIT(
To_ACCOUNT_ID,
SAMOUNT);
} catch (BEATuxBldr::TuxException *te) {
cout << "\n--- Deposit TuxException=" <<
te->GetMessage() << endl;
delete te;
Do_RollbackTransaction();
return;
}
In the sample code we have wrapped the call that rolls back a transaction in the Do_RollbackTransaction function. The NoTransaction
exception should be handled by any application that uses the Transaction::rollback
member function. The Do_RollbackTransaction function allows the application to consistently handle exceptions that may be thrown by a Transaction
.
Listing 5-18 Rolling Back a Transaction
int Do_RollbackTransaction()
{
try {
pTrans->rollback();
} catch (BEATuxBldr::NoTransaction *ne) {
cout << "\n--- Rollback trans err; NoTransaction=" <<
ne->GetMessage() << endl;
delete ne;
return FALSE;
}
return TRUE;
}
If a reply to an asynchronous request is available, it must be explicitly collected by the client. The sample application determines when a reply is available by polling. We do this for replies to both the DEPOSIT
and WITHDRAWAL
service.
The first thing we do is ensure that the calls to the ReplyCompleted member function return immediately and do not block by setting the TPNOBLOCK
flag on the pDEPOSITReply
and pWITHDRAWALReply
objects. When the reply is ready, the call to ReplyCompleted returns TRUE
and we will use the generated accessor functions of pWITHDRAWALReply
and pDEPOSITReply
to access the returned output parameters.
When we invoke the ReplyCompleted member functions of pWITHDRAWALReply
and pDEPOSITReply
to collect the reply, we must also check for exceptions by looking at values in TuxException
and the exception generated for this service (see the Generated Exception section in the API). The following exceptions are associated with services we will be using:
Service | Generated Exception |
---|---|
WITHDRAWAL |
WITHDRAWALException |
DEPOSIT |
DEPOSITException |
These exceptions are handled with in-line code, instead of calling another function in the sample application. If an exception was thrown, we have to roll back the active transaction and check for exceptions on the rollback. To keep the code more readable, the exception processing on the rollback is placed in a separate function. In the sample code we have wrapped the call that rolls back the transaction within a function called Do_RollbackTransaction. The Rolling back a Transaction section shows the code for the Do_RollbackTransaction function.
Listing 5-19
Polling for and Collecting Pending Replies
BEATuxBldr::Boolean DEPOSITDone = false;
BEATuxBldr::Boolean WITHDRAWALDone = false;
pDEPOSITReply->SetFlags(TPNOBLOCK);
pWITHDRAWALReply->SetFlags(TPNOBLOCK);
while (( ! DEPOSITDone) || ( ! WITHDRAWALDone)) {
try {
DEPOSITDone = pDEPOSITReply->ReplyCompleted();
} catch (BANKAPP::TELLER::DEPOSITException *e) {
cout << "\n--- DEPOSITException:" << endl;
cout << "\n--- STATLIN=" << e->_STATLIN << endl;
delete e;
Do_RollbackTransaction();
return;
} catch (BEATuxBldr::TuxException *te) {
cout << "\n--- Deposit TuxException="
<< te->GetMessage() << endl;
delete te;
Do_RollbackTransaction();
return;
}
try {
WITHDRAWALDone = pWITHDRAWALReply->ReplyCompleted();
} catch (BANKAPP::TELLER::WITHDRAWALException *e) {
cout << "\n--- WITHDRAWALException:" << endl;
cout << "\n--- STATLIN=" << e->_STATLIN << endl;
delete e;
Do_RollbackTransaction();
return;
} catch (BEATuxBldr::TuxException *te) {
cout << "\n--- Withdrawal TuxException="
<< te->GetMessage() << endl;
delete te;
Do_RollbackTransaction();
return;
}
}
If the calls to the WITHDRAWAL
and DEPOSIT
services complete successfully, we need to end the transaction by invoking the Transaction::commit
member function.
Listing 5-20 End a Successful Transaction
Do_CommitTransaction();
In the sample code we have wrapped the call that commits the transaction within a function called Do_CommitTransaction. The HeuristicHazard
, HeuristicMixed
, and NoTransaction
exceptions must be handled by any application that uses the Transaction::commit
member function. The Do_CommitTransaction function allows the application to consistently handle exceptions thrown by a Transaction
.
Listing 5-21 Committing a Transaction
int Do_CommitTransaction()
{
try {
pTrans->commit(FALSE);
} catch (BEATuxBldr::NoTransaction *ne) {
cout << "\n--- Commit trans err; NoTransaction=" <<
ne->GetMessage() << endl;
delete ne;
return FALSE;
} catch (BEATuxBldr::HeuristicHazard *he)
{
cout << "\n--- Commit trans err; HeuristicHazard=" <<
he->GetMessage() << endl;
delete he;
return FALSE;
} catch (BEATuxBldr::HeuristicMixed *hme) {
cout << "\n--- Commit trans err; HeuristicMixed=" <<
hme->GetMessage() << endl;
delete hme;
return FALSE;
}
return TRUE;
}
When the service call returns, we invoke the accessor functions of the pWITHDRAWALReply and pDEPOSITReply reply objects to access the output parameters. (For more information about the reply objects, see the Polling for and Collecting Pending Replies section.) For each output parameter there is a specific member function generated that returns the value of that output parameter. For example, the GetSBALANCE
member function is used to get the value of the SBALANCE output parameter from the WITHDRAWAL
service.
Listing 5-22 Display the Results of the Service Replies
cout << "\nOutput From SBALANCE: " <<
pWITHDRAWALReply->GetSBALANCE() << endl;
cout << "\nOutput From STATLIN: " <<
pWITHDRAWALReply->GetSTATLIN() << endl;
cout << "\nOutput To SBALANCE: " <<
pDEPOSITReply->GetSBALANCE() << endl;
cout << "\nOutput To STATLIN: "<<
pDEPOSITReply->GetSTATLIN() << endl;