[Top] [Prev] [Next] [Bottom]

3. Writing Service Routines


Writing Request/Response Services

The preceding chapter discussed the ATMI primitives that can be used to write client programs. In this chapter, some of the same functions are revisited in the context of the service subroutines. As you may recall, services are C subroutines that are linked together with the BEA TUXEDO system-provided main() to create executable server programs.

In this chapter the discussion covers only services that operate in a request/response mode. Conversational clients and servers are the subject of Chapter 4, "Conversational Clients and Services."

Examples Taken from the Sample Application

Most of the examples shown are taken from the services of the banking application.

Application Service Template

Since the communication details are taken care of by BEA TUXEDO system's main(), the programmer can concentrate on the application logic rather than communication implementation. For services to be compatible with the main() provided, they must adhere to certain conventions. These conventions are referred to as the service template for coding service routines; they are described here and on the tpservice(3c) reference page of the BEA TUXEDO Reference Manual.

Request/response services have the following characteristics:

The following sections examine these concepts more closely.

The TPSVCINFO Structure

The typical service routine is defined as a function receiving one argument that is a pointer to a structure. This service information structure is typdef'd as TPSVCINFO in the atmi.h header file and includes the following members:

char      name[32]; /* service name being invoked */
long flags; /* describes service attributes */
char *data; /* request data */
long len; /* request data length */
int cd; /* connection descriptor if (flags & TPCONV) true */
int appkey; /* application authentication client key */
CLIENTID cltid; /* client identifier for originating client */

The members of the structure

The name Member of TPSVCINFO

The name member of the structure indicates to the service routine the name that the requesting process used to invoke the service.

The flags Member of TPSVCINFO

The flags member of the structure is used to let the service know if it is in transaction mode or if the caller is expecting a reply. The various ways a service can be placed in transaction mode are discussed in Chapter 5, "Global Transactions in BEA TUXEDO System." If the value of flags is TPTRAN, it indicates that the service is in transaction mode. When a service is called by tpcall() or tpacall() with the flags parameter set to TPNOTRAN, it indicates that the service cannot participate in the current transaction, but it is still possible for the service to be in transaction mode. So even when the caller sets the TPNOTRAN communication flag, it is possible for TPTRAN to be set in svcinfo->flags. The case that allows this to happen is discussed in Chapter 5, "Global Transactions in BEA TUXEDO System." The flags member is set to TPNOREPLY if the service was called by tpacall() with the TPNOREPLY communication flag set. It is possible for the flags member to be set to both of these values. When this represents a valid situation is discussed in the next chapter. However, if a called service is part of the same transaction as the calling process, it must return a reply to the caller.

The data and len Members of TPSVCINFO

The data member points to a buffer that was previously allocated by tpalloc() within the server main(); this buffer is used to receive the request message. The len member contains the length of the request data that is in the buffer pointed to by data. It is recommended that you use this buffer to send back the reply message or forward the request message. This is further discussed when explaining the proper usage of the tpreturn() and tpforward() functions. The contents of the buffer get overwritten each time the service routine is invoked regardless of whether the buffer is used as the message buffer for returning or forwarding the reply.

The appkey Member of TPSVCINFO

The use of this member is left to the application to decide. If application-specific authentication is part of your design, the application-specific authentication server, which is called at the time a client joins the application, should return a client authentication key as well as a success/failure indication. (This is the logic of the BEA TUXEDO system default AUTHSVC service.) The key is held by the system on behalf of the client and is passed to subsequent service requests in the appkey field. By the time the key is passed to the service, the client has already passed authentication, but the appkey field can be used within the service to identify in some way the user invoking the service or some other parameters associated with the user. If not used, the value is set to -1 by the system.

The cltid Member of TPSVCINFO

The cltid member is a structure of type CLIENTID. It is used by the system to carry the identification of the client. You should not make changes in this structure.

Accessing Data that Comes with the Request

When accessing the request data pointed to by data, the service must be coded to expect the data to be in a buffer of the type defined for the service in the configuration file. For everything to be interpreted correctly by the system, the type and subtype of the request buffer passed by the calling process must agree with the type that is coded for the service called which, in turn, must agree with the typed buffer as defined for that service in the configuration file.

Listing 3-1 illustrates a typical service definition; this one is taken from the ABAL (account balance) service routine. ABAL is part of the BAL server.

Listing 3-1 Typical Service Definition
#include <stdio.h>      /* UNIX */
#include <atmi.h> /* TUXEDO */
#include <sqlcode.h> /* TUXEDO */
#include "bank.flds.h" /* bankdb fields */
#include "aud.h" /* BANKING view defines */

EXEC SQL begin declare section;
static long branch_id; /* branch id */
static float bal; /* balance */
EXEC SQL end declare section;

/*
* Service to find sum of the account balances at a SITE
*/

void
#ifdef __STDC__
ABAL(TPSVCINFO *transb)

#else

ABAL(transb)
TPSVCINFO *transb;
#endif

{
struct aud *transv; /* view of decoded message */

/* Set pointer to TPSVCINFO data buffer */

transv = (struct aud *)transb->data;

set the consistency level of the transaction

/* Get branch id from message, do query */

EXEC SQL declare acur cursor for
select SUM(BALANCE) from ACCOUNT;
EXEC SQL open acur; /* open */
EXEC SQL fetch acur into :bal; /* fetch */
if (SQLCODE != SQL_OK) { /* nothing found */
(void)strcpy (transv->ermsg,"abal failed in sql aggregation");
EXEC SQL close acur;
tpreturn(TPFAIL, 0, transb->data, sizeof(struct aud), 0);
}
EXEC SQL close acur;
transv->balance = bal;
tpreturn (TPSUCCESS, 0, transb->data, sizeof(struct aud), 0);
}

In Listing 3-1, the request buffer on the client side was originally allocated by a call to tpalloc() with the type parameter set to VIEW and the subtype set to aud. The ABAL service is defined in the configuration file as a service that knows about the VIEW typed buffer. (This is by implication; the BUFTYPE parameter is not specified for ABAL, which means it defaults to ALL.) ABAL's server main() allocated a buffer of the VIEW type and assigned the pointer to this buffer to the data member of the TPSVCINFO structure that was passed to the ABAL subroutine. ABAL is able to retrieve the data buffer by accessing the data member as illustrated in the above example. Note that after this buffer is retrieved and before the first database access is made, the consistency level of the transaction is specified. Refer to the "Global Transactions and Resource Managers" and the "Comprehensive Example" sections in Chapter 7 for more details on transaction consistency levels.

Checking the Buffer Type

Listing 3-2 shows the service accessing the data buffer to determine its type. This service knows about more than one buffer type and invokes the tptypes() ATMI function primitive to determine the buffer type of the received request. It also finds out the maximum size of the buffer so it knows whether to reallocate the buffer size or not. This example is derived from the ABAL service. It represents what the subroutine would look like if it accepted its request either as an aud VIEW or an FML buffer. If its attempt to determine the message type fails, it sends back a string with an error message plus an appropriate return code; otherwise it executes the segment of code that is appropriate for the buffer type. The tpreturn() function is discussed after priority; it is included in this example for completeness.

Listing 3-2 Checking for Buffer Type
#define TMTYPERR 1 /* return code indicating tptypes failed */
#define INVALMTY 2 /* return code indicating invalid message type */

void
ABAL(transb)

TPSVCINFO *transb;

{
struct aud *transv; /* view message */
FBFR *transf; /* fielded buffer message */
int repc; /* tpgetrply return code */
char typ[TMTYPELEN+1], subtyp[TMSTYPELEN+1]; /* type, subtype of message */
char *retstr; /* return string if tptypes fails */

/* find out what type of buffer sent */
if (tptypes((char *)transb->data, typ, subtyp) == -1) {
retstr=tpalloc("STRING", NULL, 100);
(void)sprintf(retstr,
"Message garbled; tptypes cannot tell what type message\n");
tpreturn(TPFAIL, TMTYPERR, retstr, 100, 0);
}
/* Determine method of processing service request based on type */
if (strcmp(typ, "FML") == 0) {
transf = (FBFR *)transb->data;
... code to do abal service for fielded buffer ...
tpreturn succeeds and sends FML buffer in reply
}
else if (strcmp(typ, "VIEW") == 0 && strcmp(subtyp, "aud") == 0) {
transv = (struct aud *)transb->data;
... code to do abal service for aud struct ...
tpreturn succeeds and sends aud view buffer in reply
}
else {
retstr=tpalloc("STRING", NULL, 100);
(void)sprintf(retstr,
"Message garbled; is neither FML buffer nor aud view\n");
tpreturn(TPFAIL, INVALMTY, retstr, 100, 0);
}
}

Checking the Priority of the Service Request

Listing 3-3 shows the fictitious PRINTER service testing the priority level of the request just received by invoking the tpgprio() function. Based on the priority level, the print job is routed to the appropriate destination printer. The contents of pbuf->data are piped to that printer. Also, pbuf->flags is queried to see if a reply is expected. If one is expected, the name of the destination printer is returned to the client. Again, the use of tpreturn() is explained in the next section.

Listing 3-3 Determining the Priority of the Received Request
#include <stdio.h>
#include "atmi.h"

char *roundrobin();

PRINTER(pbuf)

TPSVCINFO *pbuf; /* print buffer */

{
char prname[20], ocmd[30]; /* printer name, output command */
long rlen; /* return buffer length */
int prio; /* priority of request */
FILE *lp_pipe; /* pipe file pointer */

prio=tpgprio();
if (prio <= 20)
(void)strcpy(prname,"bigjobs"); /* send low priority (verbose)
jobs to big comp. center
laser printer where operator
sorts output and puts it
in a bin */
else if (prio <= 60)
(void)strcpy(prname,roundrobin()); /* assign printer on a
rotating basis to one of
many local small laser printers
where output can be picked
up immediately; roundrobin() cycles
through list of printers */
else
(void)strcpy(prname,"hispeed");
/* assign job to high-speed laser
printer; reserved for those who
need verbose output on a daily,
frequent basis */

(void)sprintf(ocmd, "lp -d%s", prname); /* output lp(1) command */
lp_pipe = popen(ocmd, "w"); /* create pipe to command */
(void)fprintf(lp_pipe, "%s", pbuf->data); /* print output there */
(void)pclose(lp_pipe); /* close pipe */

if ((pbuf->flags & TPNOREPLY))
tpreturn(TPSUCCESS, 0, NULL, 0, 0);
rlen = strlen(prname) + 1;
pbuf->data = tprealloc(pbuf->data, rlen); /* ensure enough space for name */
(void)strcpy(pbuf->data, prname);
tpreturn(TPSUCCESS, 0, pbuf->data, rlen, 0);

char *
roundrobin()

{
static char *printers[] = {"printer1", "printer2", "printer3", "printer4"};
static int p = 0;

if (p > 3)
p=0;
return(printers[p++]);
}

The tpreturn() and tpforward() Functions

tpreturn() and tpforward() are functions that indicate that a service routine has completed; they either send a reply back to the calling client or forward a request to another service for further processing.

Sending Replies

The primary function of a service routine is to process a request and return the reply to a client process. In performing this function, a service can in turn act as a requester and make request calls to other services with tpcall() or tpacall(). When tpreturn() is called, control always returns to main(). If the service has sent requests with asynchronous replies, it must receive all expected replies or invalidate them with tpcancel() before returning control to main(), otherwise the outstanding replies are automatically dropped when they are received by BEA TUXEDO system's main(), and an error is returned to the caller.

The tpreturn() function, besides marking the end of the service routine, also causes the reply message to be sent to the requester. If the client invoked the service with tpcall(), after a successful call to tpreturn(), the reply message is available in the buffer pointed to by *odata. If tpacall() was used to send the request, on success from tpreturn(), the reply message is available in the tpgetrply() buffer that is pointed to by *data. The syntax of this function is:

void
tpreturn(rval, rcode, data, len, flags) /* End service routine */
int rval, rcode;
char *data;
long len, flags;

Currently the flags argument is not used.

tpreturn() Arguments: rval

The rval parameter can be set to TPSUCCESS, TPFAIL, or TPEXIT. This value indicates whether the service has completed successfully or not on an application-level. These conditions are communicated to the calling client in the following way. When set to TPSUCCESS, the calling function succeeded, and if there is a reply message, it is in the caller's buffer. If the service terminated unsuccessfully, (that is, the logic of the application set rval to TPFAIL), an error is reported to the client process waiting for the reply. The client's tpcall() or tpgetrply() function call will fail and the tperrno variable will be set to TPESVCFAIL to indicate an application-defined failure. In the case of this kind of failure, if a reply message was expected, it will be available in the caller's buffer. If TPEXIT is set in rval, the functionality of TPFAIL is performed, but the server exits after the reply is sent back to the client. Note that if rval is not set, the default value of TPFAIL is assigned to this parameter. The impact of the value of this parameter when a process is in transaction mode is discussed in Chapter 5, "Global Transactions in BEA TUXEDO System."

The preceding discussion concerns the effect of rval if application-defined errors are the only ones that occur. If, however, tpreturn() encounters errors while processing its arguments, it sends a failed message (if a reply is expected) to the calling process. This is detected by the caller by the value placed in tperrno. In case of failed messages, tperrno is set to TPESVCERR. This situation overrides the effect of the value of rval. If this type of error occurs, no reply data is returned, and the contents of the caller's output buffer and its length remain unchanged.

If tpreturn() sends back a message in a buffer whose type is not known or not allowed by the caller (that is, the call was made with flags set to TPNOCHANGE), TPEOTYPE is returned in tperrno. Application success or failure cannot be determined, and the contents of the caller's output buffer and its length remain unchanged.

Also, the value returned in rval is not relevant in the case when tpreturn() is invoked and a time-out occurs for the call waiting on the reply. This situation overrides all others in determining the value that is returned in tperrno. tperrno is set to TPETIME and the reply data is not sent, leaving the contents and length of the caller's reply buffer unchanged. There are two types of time-outs in BEA TUXEDO. Blocking time-out was discussed when explaining the TPNOBLOCK and TPNOTIME communication flags. The other type of time-out, transaction time-out, is discussed in Chapter 5, "Global Transactions in BEA TUXEDO System."

tpreturn() Arguments: rcode

The rcode parameter can be used to return to the caller an application-defined return code. The client can access the value returned in rcode by querying the tpurcode global variable. This code is sent regardless of application success or failure; that is, it is returned in the case of success or TPESVCFAIL. As indicated, no reply messages can be sent in the other error cases.

tpreturn() Arguments: data and len

data points to the reply message that is to be returned to the client process. The message buffer must have been allocated by a previous call to tpalloc(). If you use the same buffer that was passed to the service in the svcinfo structure, you need not be concerned with buffer allocation or disposition since they are handled by the system supplied main(). In fact, it is not possible to free this buffer in the service subroutine. Any attempt to free the buffer using tpfree() quietly fails, achieving nothing. However, this buffer can be grown by a service routine with a call to tprealloc(). BEA TUXEDO treats the original buffer the same whether it has been resized or not. If a buffer other than the one that was passed to the service routine is used to return the message, it is up to the programmer to allocate it by invoking the tpalloc() function within the service routine. The buffer obtained in this way is automatically freed by tpreturn(). If the reply message does not have a data part, no buffer is required; simply set data to the NULL pointer. The len parameter indicates the amount of data in the reply buffer, and it is this value that can be accessed in the olen parameter of the tpcall() or the len parameter of the tpgetrply() function. As indicated earlier, the process acting as the client can use this returned value to test to see if the reply buffer has grown. If a reply is expected by the client, and there is no data in the reply buffer, that is, data is set to the NULL pointer, a reply with zero length is sent to the client. The pointer to and the contents of the client's buffer remain unchanged. When the data pointer is NULL, tpreturn() ignores the len parameter. If no reply is expected, that is, TPNOREPLY was set, tpreturn() ignores the buffer and length parameters and simply returns control to main(); the server process is then free to process another request.

tpreturn() Example

Listing 3-4 shows the TRANSFER service that is part of the XFER server. Basically, the TRANSFER service makes synchronous calls to the WITHDRAWAL and DEPOSIT services. It allocates a different buffer for the reply message since it must use the contents of the request buffer for the calls to both the WITHDRAWAL and the DEPOSIT services. If the call to WITHDRAWAL should fail, cannot withdraw is written to the status line of the form, the reply buffer is freed, and the rval parameter to tpreturn() is set to TPFAIL. If the call succeeds, the debit balance is retrieved from the reply buffer.

Note: The "to-account id" retrieved in the variable cr_id in Listing 3-4 is moved to the zeroth occurrence of the ACCOUNT_ID field in the transf fielded buffer. It is necessary to assign it to this position since it is this occurrence of a field in an FML buffer that is used for data dependent routing. Refer to the book Administering the BEA TUXEDO System.

A similar scenario is followed for the call to DEPOSIT. On success, the service frees the reply buffer that was allocated within the service routine and sets rval to TPSUCCESS and returns the pertinent account information to the status line.

Listing 3-4 How to Use tpreturn()
#include <stdio.h>      /* UNIX */
#include <string.h> /* UNIX */
#include "fml.h" /* TUXEDO */
#include "atmi.h" /* TUXEDO */
#include "Usysflds.h" /* TUXEDO */
#include "userlog.h" /* TUXEDO */
#include "bank.h" /* BANKING #defines */
#include "bank.flds.h" /* bankdb fields */


/*
* Service to transfer an amount from a debit account to a credit
* account
*/

void
#ifdef __STDC__
TRANSFER(TPSVCINFO *transb)

#else

TRANSFER(transb)
TPSVCINFO *transb;
#endif

{
FBFR *transf; /* fielded buffer of decoded message */
long db_id, cr_id; /* from/to account id's */
float db_bal, cr_bal; /* from/to account balances */
float tamt; /* amount of the transfer */
FBFR *reqfb; /* fielded buffer for request message*/
int reqlen; /* length of fielded buffer */
char t_amts[BALSTR]; /* string for transfer amount */
char db_amts[BALSTR]; /* string for debit account balance */
char cr_amts[BALSTR]; /* string for credit account balance */

/* Set pointr to TPSVCINFO data buffer */
transf = (FBFR *)transb->data;

/* Get debit (db_id) and credit (cr_id) account IDs */

/* must have valid debit account number */
if (((db_id = Fvall(transf, ACCOUNT_ID, 0)) < MINACCT) || (db_id > MAXACCT)) {
(void)Fchg(transf, STATLIN, 0,"Invalid debit account number",(FLDLEN)0);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
/* must have valid credit account number */
if ((cr_id = Fvall(transf, ACCOUNT_ID, 1)) < MINACCT || cr_id > MAXACCT) {
(void)Fchg(transf,STATLIN, 0,"Invalid credit account number",(FLDLEN)0);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}

/* get amount to be withdrawn */
if (Fget(transf, SAMOUNT, 0, t_amts, < 0) 0 || strcmp(t_amts,"") == 0) {
(void)Fchg(transf, STATLIN, 0, "Invalid amount",(FLDLEN)0);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
(void)sscanf(t_amts,"%f",tamt);

/* must have valid amount to transfer */
if (tamt = 0.0) {
(void)Fchg(transf, STATLIN, 0,
"Transfer amount must be greater than $0.00",(FLDLEN)0);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}

/* make withdraw request buffer */
if ((reqfb = (FBFR *)tpalloc("FML",NULL,transb->len)) == (FBFR *)NULL) {
(void)userlog("tpalloc failed in transfer\n");
(void)Fchg(transf, STATLIN, 0,
"unable to allocate request buffer", (FLDLEN)0);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
reqlen = Fsizeof(reqfb);

/* put ID in request buffer */
(void)Fchg(reqfb,ACCOUNT_ID,0,(char *)&db_id, (FLDLEN)0);

/* put amount in request buffer */
(void)Fchg(reqfb,SAMOUNT,0,t_amts, (FLDLEN)0);

/* increase the priority of withdraw call */
if (tpsprio(PRIORITY, 0L) == -1)
(void)userlog("Unable to increase priority of withdraw\n");

if (tpcall("WITHDRAWAL", (char *)reqfb,0, (char **)&reqfb,
(long *)&reqlen,TPSIGRSTRT) == -1) {
(void)Fchg(transf, STATLIN, 0,
"Cannot withdraw from debit account", (FLDLEN)0);
tpfree((char *)reqfb);
tpreturn(TPFAIL, 0,transb->data, 0L, 0);
}

/* get "debit" balance from return buffer */

(void)strcpy(db_amts, Fvals((FBFR *)reqfb,SBALANCE,0));
void)sscanf(db_amts,"%f",db_bal);
if ((db_amts == NULL) || (db_bal < 0.0)) {
(void)Fchg(transf, STATLIN, 0,
"illegal debit account balance", (FLDLEN)0);
tp

Invalidating Descriptors: tpcancel()

If a service calling tpgetrply() fails with TPETIME and decides not to wait any longer, it can invalidate the descriptor with a call to tpcancel(). If the reply ever does arrive, it is silently discarded. tpcancel() cannot be used for transaction replies (request was done without the TPNOTRAN flag); within a transaction, tpabort() does the same job of invalidating the transaction call descriptor. Listing 3-5 shows the code.

Listing 3-5 Invalidate a Reply after Timing Out
int cd1;
.
.
.
if ((cd1=tpacall(sname, (char *)audv, sizeof(struct aud),
TPNOTRAN)) == -1) {
.
.
.
}
if (tpgetrply(cd1, (char **)&audv,&audrl, 0) == -1) {
if (tperrno == TPETIME) {
tpcancel(cd1);
.
.
.
}
}
tpreturn(TPSUCCESS, 0,NULL, 0L, 0);

Forwarding Requests

The tpforward() function allows a service to forward a request to another service for further processing. This differs from a service call in that the service that forwards the request does not ever expect a reply. The reply is owed to the process that originated the request, and the responsibility for providing the reply has been passed to the service to which the request has been forwarded. It becomes the responsibility of the last server in the forward chain to send the reply back by invoking tpreturn(). The process that made the initial service call is the client and will be waiting for a reply.

The following figure gives you an idea of what a forward chain might look like. The request is initiated with a tpcall() and the eventual reply is provided by the tpreturn() that is invoked by the last service in the chain.

Figure 3-1 Forwarding a Request

Service routines can forward requests at specified priorities in the same manner that client processes send requests. You may recall that this is accomplished by invoking the tpsprio() function.

tpforward() is identical to tpreturn() in that when it is called, main() regains control, and the server process is free to do more work. The syntax of this function is:

void
tpforward(svc, data, len, flags) /* Forward request */
char *svc, *data;
long len, flags;
tpforward() Arguments

The first parameter of tpforward(), svc, is a character pointer that references the name of the service to which the request is to be forwarded. The request buffer is pointed to by its second parameter, data, and the length of the request data is available in len. These two parameters and the remaining one, flags, share the same meanings as the corresponding ones specified for tpreturn(). Recall that, at present, flags has no defined values.

Note: When acting as a client, a server process is not allowed to request services from itself when a reply is expected. If the only available instance of the desired service is offered by the server process making the request, the call will fail indicating that a recursive call would have been made. However, if the service routine sends the request with the TPNOREPLY communication flag set or forwards the request, the call will not fail since the caller is not waiting on itself.

Calling tpforward() can be used to indicate success up to that point in processing the request. If no application errors have been detected, you can invoke tpforward(); otherwise, call tpreturn() with rval set to TPFAIL.

tpforward() Example

Listing 3-6 is taken from the OPEN_ACCT service routine which is part of the ACCT server. It shows what the service would look like if it used a call to tpforward() to send its data buffer to the DEPOSIT service. The example illustrates testing of the SQLCODE to see if the account insertion was successful. If the new account is added successfully, the branch record is updated to reflect the new account. On success, the data buffer gets forwarded to the DEPOSIT service. On failure, tpreturn() is called with rval set to TPFAIL and the failure reported to the status line of the form.

Listing 3-6 How to Use tpforward()
 ...
/* set pointer to TPSVCINFO data buffer */
transf = (FBFR *)transb->data;
...
/* Insert new account record into ACCOUNT*/
account_id = ++last_acct; /* get new account number */
tlr_bal = 0.0; /* temporary balance of 0 */
EXEC SQL insert into ACCOUNT (ACCOUNT_ID, BRANCH_ID, BALANCE,
ACCT_TYPE, LAST_NAME, FIRST_NAME, MID_INIT, ADDRESS, PHONE) values
(:account_id, :branch_id, :tlr_bal, :acct_type, :last_name,
:first_name, :mid_init, :address, :phone);
if (SQLCODE != SQL_OK) { /* Failure to insert */
(void)Fchg(transf, STATLIN, 0,
"Cannot update ACCOUNT", (FLDLEN)0);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}

/* Update branch record with new LAST_ACCT */

EXEC SQL update BRANCH set LAST_ACCT = :last_acct where BRANCH_ID = :branch_id;
if (SQLCODE != SQL_OK) { /* Failure to update */
(void)Fchg(transf, STATLIN, 0,
"Cannot update BRANCH", (FLDLEN)0);
tpreturn(TPFAIL, 0, transb->data, 0L, 0);
}
/* up the priority of the deposit call */
if (tpsprio(PRIORITY, 0L) == -1)
(void)userlog("Unable to increase priority of deposit\n");

/* tpforward same buffer to deposit service to add initial balance */
tpforward("DEPOSIT", transb->data, 0L, 0);

Sending Unsolicited Messages

The BEA TUXEDO system allows unsolicited messages to be sent to client processes without disturbing the processing of request/response calls or conversational communications. Unsolicited messages can be sent to client processes by name (tpbroadcast()) or by an identifier received with a previously processed message (tpnotify()). Messages sent via tpbroadcast() can originate either in a service or in another client. Messages sent via tpnotify() can originate only in a service, as shown in the following table.

Initiator Receiver

tpbroadcast()

client, server

client

tpnotify()

server

client

tpbroadcast() Arguments

tpbroadcast() allows a message to be sent to registered clients of the application. (Registered clients are those that have successfully made a call to tpinit() and have not yet made a call to tpterm().) The syntax of the function is:

int
tpbroadcast(lmid, usrname, cltname, data, len, flags)
char *lmid, *usrname, *cltname, *data;
long len, flags;

lmid, usrname, and cltname are pointers to identifiers used to select the target list of clients. A value of NULL for any of these arguments acts as a wildcard for that argument, so the message can be directed to groups of clients or to the entire universe.

The data argument points to the content of the message up to the length specified by the len argument. If data points to a self-defining buffer type, for example, an FML buffer, len can be 0. The flags argument can be:

TPNOBLOCK
If a blocking condition exists, don't send the message.

TPNOTIME
Wait indefinitely; do not time out.

TPSIGRSTRT
When a signal interrupts any underlying system calls, the call is reissued. If this flag is not set, a signal causes tpbroadcast() to fail with the TPGOTSIG error code.
tpbroadcast() Example

Listing 3-7 shows an example of a call to tpbroadcast() where all clients are targeted. The message to be sent is in a STRING buffer.

Listing 3-7 Using tpbroadcast()
char *strbuf;

if ((strbuf = tpalloc("STRING", NULL, 0)) == NULL) {
error routine
}

(void) strcpy(strbuf, "hello, world");

if (tpbroadcast(NULL, NULL, NULL, strbuf, 0, TPSIGRSTRT) == -1)
error routine

tpnotify() Arguments

tpnotify() can be called only from a service. The syntax of the function is:

int
tpnotify(clientid, data, len, flags)
CLIENTID *clientid;
char *data;
long len, flags;

*clientid is a pointer to a CLIENTID structure saved from the TPSVCINFO structure that accompanied the service request to this service. Thus it can be seen that tpnotify() is used to direct an out-of-band message to the client process that called the service. This is not the same as the reply to the service request that would be sent when the service calls tpreturn() (or when a conversational service calls tpsend() to send a reply to the client), nor is it any part of a transaction, if one is in progress. It is used in cases where the service encounters information in processing that needs to be passed to the unsolicited message handler for the application.

The *data, len, and flags arguments are the same as they are for tpbroadcast().

Advertising, Unadvertising Services

When servers are booted, they advertise the services they offer based on the specification in their CLOPT parameter in the configuration file. The default specification calls for the server to advertise all services with which it was built; this is the meaning of the -A option. (See ubbconfig(5) or servopts(5).) When a service is advertised, it takes up a service table entry in the bulletin board. This can lead an application to decide to boot servers to offer some subset of their available services. As the servopts(5) reference page makes clear, the -s option allows a comma-separated list of services to be specified by service name. It also allows, with the -s services:func notation, for a function with a name different from that of the advertised service to be called to process the service request. The BEA TUXEDO system administrator can use the advertise and unadvertise commands of tmadmin(1) to control the services offered by servers.

The tpadvertise() and tpunadvertise() functions allow that dynamic control to be exercised within a service of a request/response server or conversational server to advertise or unadvertise a service. The limitation is that the service to be advertised (or unadvertised) must be available within the same server as the service making the request.

tpadvertise() Arguments

The syntax of tpadvertise is:

int
tpadvertise(svcname, func)
char *svcname;
void (*func);

*svcname is a pointer to a character string of 15 characters or less that names the service to be advertised. Names longer than 15 characters are truncated; a NULL value causes an error, [TPEINVAL].

func is the address of a BEA TUXEDO system service function that is called to perform the service (of course, it is not uncommon that this name is the same as the name of the service). func is not permitted to be NULL.

tpadvertise() Example

Listing 3-8 shows an example of tpadvertise() that is based on the following hypothetical situation (this is an extension to an existing bankapp server):

SERVER TLR is specified to offer only the service TLR_INIT when booted.

After some initialization, TLR_INIT advertises services DEPOSIT and WITHDRAW, both performed by function tlr_funcs, and both built into the TLR server.

On return from advertising the two services, TLR_INIT unadvertises itself.

Listing 3-8 Dynamic Advertising and Unadvertising
extern void tlr_funcs()
.
.
.
if (tpadvertise("DEPOSIT", (tlr_funcs)(TPSVCINFO *)) == -1)
check for errors;
if (tpadvertise("WITHDRAW", (tlr_funcs)(TPSVCINFO *)) == -1)
check for errors;
if (tpunadvertise("TLR_INIT") == -1)
check for errors;
tpreturn(TPSUCCESS, 0, transb->data,0L, 0);

tpunadvertise()

tpunadvertise(), of course, is called to remove a service from the service table of the bulletin board. The syntax is:

tpunadvertise(svcname)
char *svcname;

The only argument is a pointer to the svcname being unadvertised. An example is included above in Listing 3-8.

System-Supplied Servers and Subroutines

The BEA TUXEDO system is delivered with a server that provides a basic client authentication service: AUTHSVR. A standard main() routine and two subroutines called by main() are also provided.

System-Supplied Servers

The servers described in this section are intended to save you the trouble of coding services to do routine tasks.

AUTHSVR

AUTHSVR(5) can be used to provide individual client authentication for an application. It is called by tpinit(3c) when the level of security for the application is TPAPPAUTH.

The service in AUTHSVR looks in the data field of the TPINIT buffer for a user password (not to be confused with the application password in the passwd field of the TPINIT buffer). The string in data is checked against the /etc/passwd file (by default; the application can specify a different file to be checked). When used by a native site client, the data field is sent along by tpinit() as it is received. This means that if the application wants the password to be encrypted, the client program must be coded accordingly. When used by a workstation client, tpinit() encrypts the data before sending it across the network.

The BEA TUXEDO System main()

To speed the development of servers, the BEA TUXEDO system provides a predefined main() routine for server load modules. This main() is automatically included when the buildserver(1) command is executed.

The predefined main() routine does the following:

The main() that the system provides is a closed abstraction and can not be modified by the programmer. As indicated in the previous list items, it takes care of all the details concerning entrance into and exit from an application, buffer and transaction management, and communication. It leaves the programmer free to implement the application through the logic of the service subroutines. Note that as a result of the system supplied main() doing the work of joining and leaving the application, it is an error for services to make calls to the tpinit() or tpterm() functions. This error returns TPEPROTO in tperrno.

In addition to the above functionality, there are two user exits in main() that allow the programmer to do various initialization and exiting activities. The next sections explain how these two system supplied subroutines are used.

BEA TUXEDO System-Supplied Subroutines

There are two subroutines of main(), tpsvrinit() and tpsvrdone(), that are provided with the BEA TUXEDO system software. The default versions can be modified to suit your application.

tpsvrinit()

When a server is booted, the BEA TUXEDO system main() calls tpsvrinit() during its initialization phase before it handles any service requests. If an application does not provide this routine in a server, the default one is called that opens the resource manager and makes an entry in the central event log indicating that the server has successfully started. The central event log is discussed in Chapter 7, "Error Management." For now, simply understand that it is a UNIX System file to which processes can write messages by calling the userlog(3c) function. Coming as it does near the beginning of the system-supplied main(), tpsvrinit() can be used for any initialization purposes that might be needed by an application. Two possibilities are illustrated here: receiving command line options and opening a database.

Note that although not shown in the following examples, message communication can also be performed within this routine. However, tpsvrinit() fails if it returns with asynchronous replies pending. In addition, the replies are ignored by BEA TUXEDO, and the server exits gracefully. tpsvrinit() can also start and complete transactions, but this is discussed in Chapter 7, "Error Management."

The syntax of this function is:

int
tpsvrinit(argc, argv) /* Server initialization routine */
int argc;
char **argv;
Using tpsvrinit() to Receive Command Line Options

When a server is booted, before calling the tpsvrinit() routine, it reads the options specified for it in the configuration file. Using the UNIX function getopt(3C) (see a UNIX System programmer's reference manual), it reads options up to the point where it receives an EOF indication. The presence of a double dash (--) on the command line causes getopt to return an EOF. getopt places the argv index of the next argument to be processed in the external variable optind. The predefined main() then calls tpsvrinit().

Listing 3-9 shows an example of a tpsvrinit() coded to receive command line options.

Listing 3-9 Receiving Command Line Options in tpsvrinit()
tpsvrinit(argc, argv)
int argc;
char **argv;
{
int c;
extern char *optarg;
extern int optind;
.
.
.
while((c = getopt(argc, argv, "f:x:")) != EOF)
switch(c){
.
.
.
}
.
.
.
}

When the BEA TUXEDO system's main() calls tpsvrinit(), it picks up any arguments that follow the double dash (--) on the command line. In the example above, options f and x each take an argument, as indicated by the colon. optarg points to the beginning of the option argument. We have omitted the switch statement logic.

Using tpsvrinit() to Open a Resource Manager

Listing 3-10 shows a code fragment that illustrates another common use of tpsvrinit(): opening a resource manager. BEA TUXEDO provides functions to open a resource manager, tpopen() and tx_open(). It also provides the complementary functions, tpclose() and tx_close(). The details of these ATMI primitives can be found in the BEA TUXEDO Reference Manual. Applications that use these calls to open and close their resource managers are portable in this respect. They work by accessing the resource manager instance-specific information that is available in the configuration file. These calls are optional and can be used in place of the resource manager specific calls that are sometimes part of the Data Manipulation Language (DML) if the resource manager is a database. In the example that follows, the code does not pick up command line options, but there is no reason it could not both pick up options and open the database. Also, note the use of the userlog(3c) function to write to the central event log.

Listing 3-10 Opening a Resource Manager in tpsvrinit()
tpsvrinit()
{

/* Open database */

if (tpopen() == -1) {
(void)userlog("tpsvrinit: failed to open database: ");
switch (tperrno) {
case TPESYSTEM:
(void)userlog("System /T error\n");
break;
case TPEOS:
(void)userlog("Unix error %d\n",Uunixerr);
break;
case TPEPROTO:
(void)userlog("Called in improper context\n");
break;
case TPERMERR:
(void)userlog("RM failure\n");
break;
}
return(-1); /* causes the server to exit */
}
return(0);
}

If an error occurs during the initialization activities, tpsvrinit() can be coded to permit the server to exit gracefully before the server starts processing service requests.

tpsvrdone()

Using tpsvrdone() to Close a Resource Manager

As might be expected, tpsvrdone() can call on the services of tx_close() to close the resource manager in a manner analogous to the way tpsvrinit() and tx_open() are used to open it. If the application does not define a closing routine for tpsvrdone(), the BEA TUXEDO system calls the default version, which calls tx_close() and userlog() to close the resource manager and write to the central event log. The message to the log indicates that the server is about to exit.

Note: Applications choosing to write their own versions of tpsvrinit() and tpsvrdone() should remember that the default versions of these two routines call tx_open() and tx_close(), respectively. If the application writes a new version of tpsvrinit() that calls tpopen() rather than tx_open(), they should also write a new version of tpsvrdone() that calls tpclose(). In other words, the open/close pairs have to be from the same set.

tpsvrdone() is called after the server has finished processing service requests but before it exits. Since the server is still part of the system, further communication and transactions can take place within the routine. The rules that must be followed to do this properly are covered in Chapter 7, "Error Management." The syntax of this function is:

void
tpsvrdone() /* Server termination routine */

Listing 3-11 shows the typical way in which tpsvrdone() is used.

Listing 3-11 Closing a Resource Manager in tpsvrdone()
void
tpsvrdone()
{

/* Close the database */
if(tpclose() == -1)
(void)userlog("tpsvrdone: failed to close database: ");
switch (tperrno) {
case TPESYSTEM:
(void)userlog("BEA TUXEDO error\n");
break;
case TPEOS:
(void)userlog("Unix error %d\n",Uunixerr);
break;
case TPEPROTO:
(void)userlog("Called in improper context\n");
break;
case TPERMERR:
(void)userlog("RM failure\n");
break;
}
return;
}
return;
}

Compiling Subroutines to Build Servers

To compile your service subroutines, you have the same freedom you had in compiling clients. You can use regular C Compilation System utilities to make object files. The object files can be kept as individual files or collected into an archive file. If you prefer, you can retain them as source (.c) files. In any event, when you invoke buildserver to produce an executable server, you specify them on the command line with the -f option. This applies to new versions of tpsvrinit() and tpsvrdone() as well as your application subroutines.

The buildserver Command

buildserver(1) is used to put together an executable server with the BEA TUXEDO system's main(). Options identify the name of the output file, input files provided by the application, and various libraries that permit you to run a BEA TUXEDO system application in a variety of ways.

buildserver invokes the cc command. The environment variables CC and CFLAGS can be set to name an alternative compile command and to set flags for the compile and link edit phases. The key buildserver command line options are described in the paragraphs that follow.

The buildserver -o Option

The -o option is used to assign a name to the executable output file. If no name is provided, the file is named SERVER.

The buildserver -f and -l Options

The -f and -l options are used to specify files to be used in the link edit phase. The files specified in the -f option are brought in before the BEA TUXEDO system and resource manager libraries (first), whereas the files specified in the -l option are brought in after these libraries (last). There is a significance to the order of the options. The order is dependent on function references and in what libraries the references are resolved. Source modules should be listed ahead of libraries that might be used to resolve their references. Any .c files are first compiled. Object files can be either separate .o files or groups of files in archive (.a) files. If more than a single file name is given as an argument to a -f or -l option, the syntax calls for a list enclosed in double quotes. You can use as many -f and -l options as you need.

The buildserver -r Option

The -r option is used to specify which resource manager access libraries should be link edited with the executable server. The choice is specified with a string from the $TUXDIR/udataobj/RM file. Only one string can be specified. The database functions in your service are the same regardless of which library is used.

All valid strings that name resource managers are contained in the $TUXDIR/udataobj/RM file. When integrating a new resource manager into BEA TUXEDO, this file must be updated to include the information about the resource manager. Refer to the buildtms(1) reference page and the book Administering the BEA TUXEDO System for more information.

If you are using the ATMI transaction primitives, tpbegin() and tpcommit()/tpabort(), you should build your servers using the buildserver command.

The buildserver -s Option

The -s option is used to specify the service names included in the server and the name of the functions that perform each service. Normally, the function name is the same as the name of the service. In the sample program, our convention is to specify all uppercase for the service name. For example, the OPEN_ACCT service would be processed by function OPEN_ACCT(). However, the -s option of buildserver does allow you to specify an arbitrary name for the processing function for a service within a server. For example, the command

buildserver -o ACCT -f acct.o -s NEW_ACCT:OPEN_ACCT -s CLOSE_ACCT

specifies that the NEW_ACCT request is to be processed by a function called OPEN_ACCT(), while service request CLOSE_ACCT is to be processed by the function CLOSE_ACCT().

However, it is possible for the administrator to specify that only a subset of the services that were used to create the server with the buildserver command are to be advertised when the server is booted. Refer to the book Administering the BEA TUXEDO System.

Using C++

There are not many differences in using a C++ compiler instead of a C compiler to develop application servers. The two areas affected are the declaration of the service function, and the use of constructors and destructors.

When declaring a service function, it must be declared to have "C" linkage using extern "C". That is, the function prototype should be as follows:

#ifdef __cplusplus
extern "C"
#endif
MYSERVICE(TPSVCINFO *tpsvcinfo)

Declaring the name with "C" linkage ensures that the C++ compiler will not mangle the name (many C++ compilers change the function name to include type information for the parameters and function return). This allows for the linking of both C and C++ service routines into a single server without the application programmer indicating what type each routine is. This also allows use of dynamic service advertisement, which requires accessing the symbol table of the executable to find the function name.

C++ constructors are called to initialize class objects when they are created, and destructors are invoked when class objects are destroyed. For automatic (local, non-static) variables that have constructors and destructors, the constructor is called when the variable comes into scope and the destructor is called when the variable goes out of scope. However, when tpreturn() or tpforward() is called, it does a non-local goto (using longjmp(3)) such that destructors for automatic variables will not be called. To avoid this problem, the application should be written such that tpreturn() or tpforward() is called from the service routine (instead of any functions called from the service routine). In addition, either the service routine should not have any automatic variables with destructors (they should be declared and used in a function called by the service routine) or they should be declared and used in a nested scope (within curly brackets {} ) such that the scope ends before calling tpreturn() or tpforward(). Summarizing, there should be no automatic variables with destructors in scope in the current function or on the stack when tpreturn() or tpforward() is called.

For proper handling of global and static variables that have constructors and destructors, many C++ compilers require that the main() must be compiled using the C++ compiler (special processing is included in the main() to ensure that the constructors are executed when the program starts and the destructors are executed when the program exits). Since the main() is provided by the BEA TUXEDO system, the application programmer does not compile it directly. To ensure that the file is compiled using C++, the buildserver command must use the C++ compiler. This is done by setting the CC environment variable to the full pathname for the C++ compiler and setting the CFLAGS environment variable to any options to be provided on the C++ command line.



[Top] [Prev] [Next] [Bottom]