Business Agents Running in Transactions

In this section:

The engine manages transactions, invoking code registered by the business agent when the transaction edge is reached. To join a transaction, the business agent must register a class meeting the XDTx interface, supporting the prepare(), commit(), and rollback() methods. Dealing with the transaction completion is the responsibility of the business agent.

Transactions can be local or global. A local transaction is a business agent group process flow running for a single message. If the local transaction state has been set (a flag in the listener) a worker-specific transaction is begun. The transaction ends when a business agent throws an exception or the business agent group ends successfully.

A global transaction is one begun on the XDMTLclMaster() by an outside caller. The beginTx() call associates the local master with the transaction. The outside caller later issues the commit(), rollback(), or prepare() methods as appropriate to each instance of the XDMTLclMaster in the transaction. Establishing global transactions is performed by the server, and cannot be directly configured within the server.

The business agent calls the getTID() method to obtain a string of the transaction identifier assigned by the engine. If the getTID() call returns null, there is no transaction available for this business agent to join.

If a transaction is available, the business agent uses the storeTx() method to register its transaction handler. This is the equivalent of a transaction join.

Object storeTx(String tid, Object o);

The storeTx() method returns the transaction handler class to be used by the transaction manager. Usually, this is the class that you attempted to store. If the transaction manager finds a duplicate class, it merges the transaction classes and returns the class of the appropriate type. In determining the class duplication, it uses the object hashCode method. We recommend that writers provide their own hashCode method to control this duplication. You must work with the returned class reference rather than the reference to your constructed class. Once you have the reference, you can set values into the transaction handler class. Your code must take into account that the classes may be merged.

If your class can handle XA two-phase commit, you should provide a full implementation of the prepare() method. Otherwise, return XDTx.SUCCESS. You report whether you can handle two-phase commit with the canPrepare() method return.

Your business agent delegates to the transaction handler the actual execution of the commit and rollback code, which is invoked after your business agent has completed its process() method.

Methods in the XDTx Interface Class

Method

Returns

Description

canPrepare

boolean

Specifies whether the interface can support two-phase commit.

commit

int

Commits the transaction.

prepare

int

Prepares to commit.

rollback

int

Rolls back the transaction.

The business agent code is prepared normally. A section of the code is isolated into an object which is used by the business agent to house references and code for the transaction completion activity.

As the business agent operates, it passes a reference to its transaction object back to the server. It uses the storeTX() method to do this. On completion of the business agent stack or process flow, the server's transaction system holds references to the stored transaction objects.

Assume that one of the business agents encounters an unrecoverable condition. This might be a program bug that is trapped by the server, or it might be a logical condition that causes the business agent to throw an exception.

The local transaction manager intercepts the exception and steps though the list of stored transaction objects, sending a rollback() call to each. It is the responsibility of the stored transaction object to affect the rollback(). If no exception is encountered, the local transaction manager instead takes the following actions:

  1. For each transaction object that returns true from the canPrepare() method, a prepare() is issued.
  2. If all prepare()s return successfully, the transaction manager issues commit() calls to the transaction object. Commit sends commit() to any outstanding emitters.
  3. If any prepare() call returns that the prepare was unsuccessful, the transaction manager sends rollback() calls to all of the stored transaction objects. It also sends rollbacks to any outstanding (uncommitted) emitters.



x
Sample Transaction Handler

This example is taken from the XDJDBCAgent, which registers an inner class to handle transactions if it finds a Transaction ID (TID). All JDBC commit and rollback functions are handled by this class. The JDBC agent does not close the connections until the transaction is complete. The handler closes all JDBC connections regardless of which instance of the JDBC agent opened the connection.

class TransControl implements XDTx
{
    ArrayList conList;
    int len;
    Connection con;
    TransControl()
    {
        conList = new ArrayList();
    }
    void addConnection(Connection c)  // add a connection to this transaction
    {
        if (!conList.contains(c))
        {
            conList.add(c);
        }
    }
    public int hashCode()  // all JDBC connections are on a single object
    {
        return "jdbc".hashCode();
    }
    /**
      *Commit the transaction
      *
      */
    public int commit()
    {
        int rc = XDTx.SUCCESS;
        len = conList.size();
        for (int i=0;i<len;i++)
        {
            con=(Connection)conList.get(i);
            if (con != null)
            {
                try
                {
                    if (!con.isClosed())  // make certain we are still active
                    {
                        con.commit();
                        con.close();
                    }
                }
                catch (SQLException e)
                {
                    rc = XDTx.FAIL;
                }
            }
        }
        return rc;
    }
    /**
      * Roll back the transaction
      * 
      * @return SUCCESS, FAIL, or HEURISTIC
      */
    public int rollback()
    {
        int rc = XDTx.SUCCESS;
        len = conList.size();
        for (int i=0;i<len;i++)
        {
            con=(Connection)conList.get(i);
            if (con != null)
            {
                try
                {
                    if (!con.isClosed())
                    {
                        con.rollback();
                        con.close();
                    }
                }
                catch (SQLException e)
                {
                    rc = XDTx.FAIL;
                }
            }
        }
        return rc;
    }
    /**
      * Business agent does not support 2 phase commit, so always return SUCCESS
      */
    public int prepare()
    {
        return XDTx.SUCCESS;
    }
   public boolean canPrepare(){return false;}
}

Top of page

x
Calling Code for Registering With the Transaction Handler
String tid = getTID();  // get the transaction identifier TransControl tctl=null;
if (tid != null)  // if in a transaction, set up for control
{
    trace(XDState.DEBUG,"Agent in transaction "+tid);
    tctl = (TransControl) storeTx(tid,new TransControl());
}

Notice that the transaction handler returns the transaction object, which may be the one you are attempting to register, or it may be the one already registered. You should always use the object returned from the storeTx() method. The scope of the object is the transaction in progress.

Your program may need to check at various points whether a transaction is in use. For example, if no transaction is in use, you close the connections and commit the work. If a transaction is in use, leave this to the transaction controller to call your registered transaction control object to do the commit for you.


iWay Software