This example will demonstrate XA recovery in WildFly with a HornetQ XA resource and a "buggy" XA resource.
The example application will invoke an EJB which will send a JMS message in a transaction.
The server will crash while the transaction has not been committed (it is in the prepared state).
On server restart, the transaction will be recovered and the JMS message will finally be sent.
The example application will then receive the message.
The example leverages the JBoss Arquillian framework to run a WildFly instance and deploy the MDB.
In previous versions of JBoss Application Server (the precursor to WildFly) the XA recovery configuration was manual. However, in WildFly the XA recovery configuration is completely automated.
download WildFly 8.0.0.Final from here and install.
set the JBOSS_HOME property to point to the WildFly install directory
type mvn verify
from the example directory to run
The example code is composed of 3 main classes:
XARecoveryExampleStepOne
and XARecoveryExampleStepTwo
XARecoveryExampleBean
Let's take a look at XARecoveryExampleStepOne first.
Properties env = new Properties();
env.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
initialContext = new InitialContext(env);
XARecoveryExampleService service = (XARecoveryExampleService) initialContext.lookup("ejb:/test//XARecoveryExampleBean!org.apache.activemq6.javaee.example.server.XARecoveryExampleService");
send
method. This method will send a JMS text message (with the text passed in parameter)
and crash the server when committing the transaction
String message = "This is a text message sent at " + new Date();
System.out.println("invoking the EJB service with text: " + message);
try
{
service.send(message);
}
catch (Exception e)
{
System.out.println("#########################");
System.out.println("The server crashed: " + e.getMessage());
System.out.println("#########################");
}
At that time, the server is crashed and is automatically restarted by the test runner (i.e. XARecoveryRunnerTest).
Let's take a look at XARecoveryExampleStepTwo now.
boolean received = false;
while (!received)
{
try
{
Thread.sleep(15000);
receiveMessage();
received = true;
}
catch (Exception e)
{
System.out.println(".");
}
}
The receiveMessage()
method contains code to receive a text message from the
JMS Queue and display it.
finally
block.
finally
{
if (initialContext != null)
{
initialContext.close();
}
}
Let's now take a look at the EJB example
In order to crash the server while a transaction is prepared, we will use a failing XAResource
which will crash the server (calling Runtime.halt()
) in its commit phase.
We will manage ourselves the transaction and its resources enlistment/delistment to be sure that the failing XAResource will crash the server after the JMS XA resources is prepared but before it is committed.
ic = new InitialContext();
TransactionManager tm = (TransactionManager)ic.lookup("java:/TransactionManager");
java:/JmsXA
)
XAConnectionFactory cf = (XAConnectionFactory)ic.lookup("java:/JmsXA");
Queue queue = (Queue)ic.lookup("queue/testQueue");
xaConnection = xacf.createXAConnection();
XASession session = xaConnection.createXASession();
MessageProducer messageProducer = session.createProducer(queue);
FailingXAResource
. For this example purpose, this XAResource implementation will
call Runtime.halt()
from its commit()
method
XAResource failingXAResource = new FailingXAResource();
tm.begin();
Transaction tx = tm.getTransaction();
tx.enlistResource(failingXAResource);
tx.enlistResource(session.getXAResource());
TextMessage message = session.createTextMessage(text);
messageProducer.send(message);
System.out.format("Sent message: %s (%s)\n", message.getText(), message.getJMSMessageID());
tx.delistResource(failingXAResource);
tx.delistResource(session.getXAResource());
System.out.println("committing the tx");
tx.commit();
When the transaction is committed, it will prepare both XAResources and then commit them.
The failing resources will crash the server leaving the JMS XA Resource prepared but not committed
When WildFly is restarted, it will automatically trigger a recovery phase. During that phase, HornetQ resources will be scanned and the prepared transaction will be recovered and committed. It is then possible to consume this message