- Introduction
- Sharing Transactions
- Crash Recovery
- Summary
- References
Sharing Transactions
Transaction inflow serves the purpose of injecting transactions from an EIS into the J2EE world. These transactions are initiated by the EIS. If a message arrived from the EIS in a transaction, the J2EE application should import the transaction context and perform the business process on that message in the same transaction. In MoonTravel's use case, the FRS asks the RMS to act on the booking message before it confirms the parties involved. If the RMS is unable to update its database for some reason, the transaction can be rolled back and retried later. In other words, each transaction is shared between the EIS and the J2EE worlds.
Importing the Transactions
It's easy to understand the procedures involved in importing transactions from an EIS. This is the task list:
- The EIS creates a transaction.
- The EIS sends a message along with a transactional context to the resource adapter. The transactional context can be specific to the EIS.
- The resource adapter extracts the EIS-specific transactional context into a standard javax.transaction.xa.Xid.
- The resource adapter creates an ExecutionContext with Xid and probably with an attribute to indicate the transaction timeout.
- The resource adapter submits the Work instance, coupled with the newly created ExecutionContext, to the application server.
- The application server reads the message and understands that the work is to be performed in a transaction. It enlists the Work instance in this imported transaction.
Upon the return of the Work instance, the EIS can commit the transaction either in a one-phase (1PC) manner or a two-phase (2PC) manner, the fundamental difference being that the 1PC doesn't need to call prepare before committing.
Following are the steps in a 2PC transaction:
- The EIS sends a prepare message to the resource adapter for the given transaction.
- The resource adapter calls prepare on an XATerminator obtained from BootstrapContext with Xid. It returns the result of the prepare call.
- Depending on the result, the EIS sends a commit or a rollback to the resource adapter.
- The resource adapter performs the commit or rollback.
The EIS can coordinate transactions with the resource adapter in many ways. For example, the transactional context, along with completion and recovery calls, can be published via a common messaging scheme between the EIS and the resource adapter. In a simple implementation, a business message produced by the EIS may contain Xid transaction-timeout attributes as message headers or properties. The resource adapter understands how to retrieve the transactional context from these properties. When a transaction is prepared for completion, the EIS sends a prepare message and the commit message for committing the transaction.
Now that we've explored the theory, let's delve into implementation details.
The Resource Adapter
The MoonTravel developers built an FRSResourceAdapter that acts a bridge between the RMS and the FRS, as shown in Listing 1. This resource adapter is bound to the J2EE application server's address space and acts on behalf of the FRS. The major requirement is to import the transaction that has been initialized by the EIS.
Listing 1 FRSResourceAdapter.java
public class FRSResourceAdapter implements ResourceAdapter { private XATerminator _xaTerminator = null; private WorkManager _workManager = null; public void start(BootstrapContext ctx){ // get a reference to XATerminator and WorkManager xaTerminator = ctx.getXATerminator(); _workManager = ctx.getWorkManager(); .... } public void endpointActivation(MessageEndpointFactory endpointFactory, ActivationSpec spec) throws ResourceException { try { // Create a worker and submit for execution FRSWork worker = new FRSWork(spec, endpointFactory, _workManager, _xaTerminator); // Now schedule this worker _workManager.scheduleWork(worker); } catch (UnavailableException e) { .. } } ... }
References to WorkManager and XATerminator are provided by the application server via BootstrapContext during the start method invocation. These objects are used to submit the work instances and complete the imported transactions, respectively. The XATerminator methods are invoked by the resource adapter, depending on the transactional attributes obtained from the EIS messages.
The Work Instance
A Work object is essentially a thread managed by the application server. The FRSWork shown in Listing 2 is instantiated and submitted to the application server for execution during the endpointActivation call.
Listing 2 FRSWork.java
public void FRSWork extends javax.resource.spi.Work { ... public FRSWork(ActivationSpec spec, MessageEndpointFactory listener, WorkManager workManager) throws UnavailableException{ // Hold these references for the lifetime _xaSpec = (FRSActivationSpec) spec; _endpointFactory = endpointFactory; _workManager = workManager;} } ... public void run() { while(ok){ // This recv method consumes the messages incessantly msg = connection.recv(); // Do the processing if a message is received try { //Check what kind of message it is if (msg instanceof PrepareSignal) { // EIS making a prepare call System.out.println(" PrepareSignal message"); PrepareSignal psMsg = (PrepareSignal)msg; // We should convert EIS call to // our XATerminator's call int XA_RESULT = _xaTerminator.prepare((Xid)psMsg.getObjectProperty(FRS_XID)); // Now that we have the result of prepare, we have to notify // EIS by sending a message from our RA notifyPrepareResultToEIS(psMsg, XA_RESULT); } else if (msg instanceof CommitSignal) { // EIS making a commit call System.out.println(" Commit Signal message"); // The Boolean argument stands for // one-phase or two-phase commit CommitSignal commitMsg = (CommitSignal)msg; _xaTerminator.commit((Xid) commitMsg.getObjectProperty(FRS_XID), false); } else if (msg instanceof FRSMessage){ // Business message, possibly in a transaction FRSMessage frsMsg = (FRSMessage) msg; // Create ExecutionContext from EIS Message. We are // expecting a transaction Xid and timeout if this // work is to be performed under a transaction execCtx = createExecutionContext(frsMsg); // Schedule for work with this ExecutionContext _workManager.scheduleWork(new TxWork(_endpointFactory, frsMsg), WorkManager.IMMEDIATE, execCtx, null); } } catch (Exception e) { .. } ... }
Pay attention to the FRSWork object's run method. When the application server executes FRSWork, it calls the run method, which checks for a message from the EIS by calling the recv method on FRSConnection. The recv method works incessantly to retrieve any messages via the Connection interface. In our simple implementation, the EIS can produce three kinds of messages, as shown in the following table.
Message |
Description |
PrepareSignal |
Indicates that the transaction(n) with the Xid is ready to prepare. Upon receiving this message, the resource adapter calls the prepare call on XATerminator. |
CommitSignal |
Indicates that the transaction(n) with the Xid is to be committed. The resource adapter will now commit the transaction using XATerminator. |
FRSMessage |
Business message with a transaction Xid. The EIS expects to execute the message under the given transaction. |
The ExecutionContext Object
The ExecutionContext provides a context in which the Work instance is to be executed. The resource adapter creates the ExecutionContext by extracting the transaction information from the FRSMessage, as shown in Listing 3.
Listing 3 Creating an ExecutionContext
public ExecutionContext createExecutionContext(FRSMessage msg) throws JMSException, NotSupportedException { // Get Xid and transaction timeout from message // FRS_XID and FRS_TX_TIMEOUT are the properties // set on this message by EIS Xid xid = (Xid) msg.getObjectProperty("FRS_XID"); long txTimeOut = msg.getLongProperty("FRS_TX_TIMEOUT"); // Create an ExecutionContext object with these values ExecutionContext execCtx = new ExecutionContext(); execCtx.setXid(xid); execCtx.setTransactionTimeout(10000); return execCtx; }
Whenever a Work instance is executed by the application server, an optional ExecutionContext can be provided. By default, a null ExecutionContext is provided, which indicates that the Work is not in a transaction. The transactional work is performed in the TxWork object, as shown in Listing 4.
Listing 4 The TxWork run Method
public void run() { try { // Create an endpoint listener. You can implement a // sophisticated MDBs pool here. For example, before creating // an endpoint, check whether one is available. If not available, // check the pool to grab one, etc. _endpointListener = (FRSListener)_endpointFactory.createEndpoint(null); // Now that we have the EIS message, // process it under the transaction _endpointListener.onMessage(_frsMsg); } catch (UnavailableException e) { ... } ... }
All the work performed in this run method is executed under the imported EIS transaction. From here on, the usual J2EE transaction concepts apply.
Transaction Completion
The EIS must send prepare/commit messages depending on the one-phase or two-phase commit criteria. The resource adapter calls prepare on the XATerminator instance by passing the transaction's Xid when it receives a prepare call. The result of the prepare call is passed back to the EIS via the notifyPrepareResultToEIS method, as shown in Listing 5.
Listing 5 Notifying the Prepare Result to EIS
private void notifyPrepareResultToEIS(EISMessage psSignal, int xa_result) { // Logic to inform the EIS about the prepare call // This uses the outbound connectivity for connecting to the EIS FRSConnection connection = FRSConnectionFactory.createConnection(); PrepareSignalResult PREPARE_MESSAGE = new PrepareSignalResult(psSignal, xa_result); connection.sendPrepareMessage(PREPARE_MESSAGE); } }
To send the prepare outcome, the resource adapter should get a connection to post a message to the EIS's inbound channel.
Once the outcome of the prepare call is received, the EIS decides whether to commit or roll back the transaction. The procedure is the same as that for prepare:
- The EIS sends a commit/rollback message with a transaction Xid.
- The resource adapter calls commit/rollback on the XATerminator object.
- XATerminator commits/rolls back the transaction marked with the Xid.