activemq-artemis/examples/javaee/xarecovery/readme.html

201 lines
9.0 KiB
HTML
Raw Normal View History

<html>
<head>
<title>ActiveMQ XA Recovery Example</title>
<link rel="stylesheet" type="text/css" href="../../common/common.css" />
<link rel="stylesheet" type="text/css" href="../../common/prettify.css" />
<script type="text/javascript" src="../../common/prettify.js"></script>
</head>
<body onload="prettyPrint()">
<h1>Java EE XA Recovery Example</h1>
<p>This example will demonstrate XA recovery in WildFly with a ActiveMQ XA resource and a "buggy" XA resource.</p>
<p>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).<br />
On server restart, the transaction will be recovered and the JMS message will finally be sent.<br />
The example application will then receive the message.<br />
<p>The example leverages the JBoss Arquillian framework to run a WildFly instance and deploy the MDB.</p>
<h3>XA Recovery configuration</h3>
<p>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.</p>
<h2>Example step-by-step</h2>
<p><i>download WildFly 8.0.0.Final from <a href="http://wildfly.org/downloads/">here</a> and install.</i></p>
<p><i>set the JBOSS_HOME property to point to the WildFly install directory</i></p>
<p><i>type <code>mvn verify</code> from the example directory to run</i></p>
<p>The example code is composed of 3 main classes:</p>
<dl>
<dt><code>XARecoveryExampleStepOne</code> and <code>XARecoveryExampleStepTwo</code></dt>
<dd>the client application to invoke the EJB and receive the message</dd>
<dt><code>XARecoveryExampleBean</code></dt>
<dd>a Stateless EJB3 which performs all the XA logic</dd>
</dl>
<h3>Example Application</h3>
<p>Let's take a look at XARecoveryExampleStepOne first.</p>
<ol>
<li>First we need to get an initial context so we can look-up the JMS connection factory and destination objects from JNDI. This initial context will get it's properties from the <a href="config/jndi.properties">jndi.properties</a></li>
</li>
<pre class="prettyprint">
<code>
Properties env = new Properties();
env.put(Context.URL_PKG_PREFIXES, "org.jboss.ejb.client.naming");
initialContext = new InitialContext(env);
</code>
</pre>
<li>We look up the EJB</li>
<pre class="prettyprint">
<code>XARecoveryExampleService service = (XARecoveryExampleService) initialContext.lookup("ejb:/test//XARecoveryExampleBean!org.apache.activemq.javaee.example.server.XARecoveryExampleService");</code>
</pre>
<li>We invoke the EJB's <code>send</code> method. This method will send a JMS text message (with the text passed in parameter)
and crash the server when committing the transaction</li>
<pre class="prettyprint">
<code>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("#########################");
}</code>
</pre>
<p><em>At that time, the server is crashed and is automatically restarted by the test runner (i.e. XARecoveryRunnerTest).</em></p>
</ol>
<p>Let's take a look at XARecoveryExampleStepTwo now.</p>
<ol>
<li>We will try to receive a message. Once the server is restarted, the message will be recovered and the consumer will receive it.</li>
<pre class="prettyprint">
<code>boolean received = false;
while (!received)
{
try
{
Thread.sleep(15000);
receiveMessage();
received = true;
}
catch (Exception e)
{
System.out.println(".");
}
}</code>
</pre>
<p>The <code>receiveMessage()</code> method contains code to receive a text message from the
JMS Queue and display it.</p>
<li>And finally, <b>always</b> remember to close your resources after use, in a <code>finally</code> block.</li>
<pre class="prettyprint">
<code>finally
{
if (initialContext != null)
{
initialContext.close();
}
}</code>
</pre>
</ol>
<p>Let's now take a look at the EJB example</p>
<p>In order to crash the server while a transaction is prepared, we will use a <em>failing</em> <code>XAResource</code>
which will crash the server (calling <code>Runtime.halt()</code>) in its commit phase.</p>
<p>We will manage ourselves the transaction and its resources enlistment/delistment to be sure that the failing XAResource
will crash the server <em>after</em> the JMS XA resources is prepared but <em>before</em> it is committed.</p>
<ol>
<li>First, we create a new initial context</li>
<pre class="prettyprint">
<code>ic = new InitialContext();</code>
</pre>
<li>We look up the Transaction Manager</li>
<pre class="prettyprint">
<code>TransactionManager tm = (TransactionManager)ic.lookup("java:/TransactionManager");</code>
</pre>
<li>We look up the JMS <em>XA</em> Connection Factory (which is bound to <code>java:/JmsXA</code>)</li>
<pre class="prettyprint">
<code>XAConnectionFactory cf = (XAConnectionFactory)ic.lookup("java:/JmsXA");</code>
</pre>
<li>We look up the JMS Queue</li>
<pre class="prettyprint">
<code>Queue queue = (Queue)ic.lookup("queue/testQueue");</code>
</pre>
<li>We create a JMS XA connection, a XA session and a message producer for the queue</li>
<pre class="prettyprint">
<code>xaConnection = xacf.createXAConnection();
XASession session = xaConnection.createXASession();
MessageProducer messageProducer = session.createProducer(queue);</code>
</pre>
<li>We create a <code>FailingXAResource</code>. For this example purpose, this XAResource implementation will
call <code>Runtime.halt()</code> from its <code>commit()</code> method</li>
<pre class="prettyprint">
<code>XAResource failingXAResource = new FailingXAResource();</code>
</pre>
<li>We begin the transaction and retrieve it from the transaction manager</li>
<pre class="prettyprint">
<code>tm.begin();
Transaction tx = tm.getTransaction();</code>
</pre>
<li>We enlist the failing XAResource</li>
<pre class="prettyprint">
<code>tx.enlistResource(failingXAResource);</code>
</pre>
<li>We enlist the <em>JMS</em> XA Resource</li>
<pre class="prettyprint">
<code>tx.enlistResource(session.getXAResource());</code>
</pre>
<li>We create a text message with the text passed in parameter of the EJB method and send it</li>
<pre class="prettyprint">
<code>TextMessage message = session.createTextMessage(text);
messageProducer.send(message);
System.out.format("Sent message: %s (%s)\n", message.getText(), message.getJMSMessageID());</code>
</pre>
<li>We delist the failing XAResource</li>
<pre class="prettyprint">
<code>tx.delistResource(failingXAResource);</code>
</pre>
<li>We delist the <em>JMS</em> XA Resource</li>
<pre class="prettyprint">
<code>tx.delistResource(session.getXAResource());</code>
</pre>
<li>We commit the transaction</li>
<pre class="prettyprint">
<code>System.out.println("committing the tx");
tx.commit();</code>
</pre>
<p>When the transaction is committed, it will prepare both XAResources and then commit them.<br />
<p>The failing resources will crash the server leaving the JMS XA Resource <em>prepared</em> but not <em>committed</em></p>
<p>When WildFly is restarted, it will automatically trigger a recovery phase. During that phase, ActiveMQ resources will be
scanned and the <em>prepared</em> transaction will be recovered and committed. It is then possible to consume this message</p>
</body>
</html>