[jboss-cvs] JBossAS SVN: r65842 - in trunk: server/src/main/org/jboss/jms/asf and 10 other directories.

jboss-cvs-commits at lists.jboss.org jboss-cvs-commits at lists.jboss.org
Thu Oct 4 10:34:29 EDT 2007


Author: adrian at jboss.org
Date: 2007-10-04 10:34:28 -0400 (Thu, 04 Oct 2007)
New Revision: 65842

Added:
   trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolFactory.java
   trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolLoader.java
   trunk/server/src/main/org/jboss/jms/asf/StdServerSession.java
   trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPool.java
   trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPoolFactory.java
   trunk/transaction/build.bat
   trunk/transaction/build.sh
   trunk/transaction/build.xml
   trunk/transaction/component-info.xml
   trunk/transaction/jbossbuild.xml
   trunk/transaction/pom.xml
   trunk/transaction/src/etc/default.mf
   trunk/transaction/src/main/org/jboss/tm/CoordinatorFactory.java
   trunk/transaction/src/main/org/jboss/tm/GlobalId.java
   trunk/transaction/src/main/org/jboss/tm/JBossRollbackException.java
   trunk/transaction/src/main/org/jboss/tm/JBossXAException.java
   trunk/transaction/src/main/org/jboss/tm/LocalId.java
   trunk/transaction/src/main/org/jboss/tm/OTSContextFactory.java
   trunk/transaction/src/main/org/jboss/tm/ResourceFactory.java
   trunk/transaction/src/main/org/jboss/tm/StringRemoteRefConverter.java
   trunk/transaction/src/main/org/jboss/tm/TMUtil.java
   trunk/transaction/src/main/org/jboss/tm/TransactionImpl.java
   trunk/transaction/src/main/org/jboss/tm/TransactionManagerInitializer.java
   trunk/transaction/src/main/org/jboss/tm/TransactionManagerService.java
   trunk/transaction/src/main/org/jboss/tm/TransactionManagerServiceMBean.java
   trunk/transaction/src/main/org/jboss/tm/TxManager.java
   trunk/transaction/src/main/org/jboss/tm/XidFactory.java
   trunk/transaction/src/main/org/jboss/tm/XidFactoryBase.java
   trunk/transaction/src/main/org/jboss/tm/XidFactoryImpl.java
   trunk/transaction/src/main/org/jboss/tm/XidFactoryMBean.java
   trunk/transaction/src/main/org/jboss/tm/XidImpl.java
   trunk/transaction/src/main/org/jboss/tm/integrity/AbstractTransactionIntegrity.java
   trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransaction.java
   trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionIntegrity.java
   trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionMBean.java
   trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrity.java
   trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrityFactory.java
   trunk/transaction/src/main/org/jboss/tm/package.html
   trunk/transaction/src/main/org/jboss/tm/recovery/BatchLog.java
   trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogReader.java
   trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogger.java
   trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerService.java
   trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerServiceMBean.java
   trunk/transaction/src/main/org/jboss/tm/recovery/BatchWriter.java
   trunk/transaction/src/main/org/jboss/tm/recovery/CorruptedLogRecordException.java
   trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatus.java
   trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLog.java
   trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLogReader.java
   trunk/transaction/src/main/org/jboss/tm/recovery/HexDump.java
   trunk/transaction/src/main/org/jboss/tm/recovery/LogRecord.java
   trunk/transaction/src/main/org/jboss/tm/recovery/LogRestarter.java
   trunk/transaction/src/main/org/jboss/tm/recovery/PendingWriteRequest.java
   trunk/transaction/src/main/org/jboss/tm/recovery/Recoverable.java
   trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogReader.java
   trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogger.java
   trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLoggerInstance.java
   trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManager.java
   trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerService.java
   trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerServiceMBean.java
   trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryTestingException.java
   trunk/transaction/src/main/org/jboss/tm/recovery/SimpleHeuristicStatusLogReader.java
   trunk/transaction/src/main/org/jboss/tm/recovery/TransactionCompletionLogger.java
   trunk/transaction/src/main/org/jboss/tm/recovery/TxCompletionHandler.java
   trunk/transaction/src/main/org/jboss/tm/recovery/XAResourceAccess.java
   trunk/transaction/src/main/org/jboss/tm/recovery/XAWork.java
   trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationService.java
   trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationServiceMBean.java
   trunk/transaction/src/main/org/jboss/tm/recovery/test/TestForceTime.java
   trunk/transaction/src/main/org/jboss/tm/recovery/test/TestOracleXA.java
   trunk/transaction/src/main/org/jboss/tm/remoting/ClientInvocationHandler.java
   trunk/transaction/src/main/org/jboss/tm/remoting/Invocation.java
   trunk/transaction/src/main/org/jboss/tm/remoting/RemoteProxy.java
   trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransaction.java
   trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransactionObjectFactory.java
   trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Coordinator.java
   trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/HeuristicHazardException.java
   trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/RecoveryCoordinator.java
   trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Resource.java
   trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Status.java
   trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Synchronization.java
   trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/SynchronizationUnavailableException.java
   trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Terminator.java
   trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionAlreadyPreparedException.java
   trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionFactory.java
   trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionInactiveException.java
   trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionNotPreparedException.java
   trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TxPropagationContext.java
   trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Vote.java
   trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMInvocationHandler.java
   trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMServant.java
   trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManager.java
   trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManagerMBean.java
Modified:
   trunk/build/build.xml
Log:
[JBAS-4516] - Revert old TM delete so the server boots

Modified: trunk/build/build.xml
===================================================================
--- trunk/build/build.xml	2007-10-04 14:03:50 UTC (rev 65841)
+++ trunk/build/build.xml	2007-10-04 14:34:28 UTC (rev 65842)
@@ -110,6 +110,7 @@
       <module name="system-jmx"/>
       <module name="testsuite"/>
       <module name="tomcat"/>
+      <module name="transaction"/>
       <module name="varia"/>
       <module name="webservices"/>
 
@@ -127,7 +128,8 @@
       </group>
 
       <group name="basic">
-        <include modules="security,
+        <include modules="transaction,
+                          security,
                           server,
                           deployment" />
 
@@ -614,6 +616,18 @@
      <ant antfile="build-distr.xml" target="_module-management-all"/>
   </target>
 
+  <!-- =========== -->
+  <!-- Transaction -->
+  <!-- =========== -->
+
+  <target name="_module-transaction-most">
+     <ant antfile="build-distr.xml" target="_module-transaction-most"/>
+  </target>
+
+  <target name="_module-transaction-all" depends="_module-transaction-most">
+     <ant antfile="build-distr.xml" target="_module-transaction-all"/>
+  </target>
+
   <!-- ======= -->
   <!-- Console -->
   <!-- ======= -->

Copied: trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolFactory.java (from rev 65808, trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolFactory.java)
===================================================================
--- trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolFactory.java	                        (rev 0)
+++ trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolFactory.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,90 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.jms.asf;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MessageListener;
+import javax.jms.ServerSessionPool;
+import org.jboss.tm.XidFactoryMBean;
+
+/**
+ * Defines the model for creating <tt>ServerSessionPoolFactory</tt> objects. <p>
+ *
+ * @author    <a href="mailto:peter.antman at tim.se">Peter Antman</a> .
+ * @author    <a href="mailto:hiram.chirino at jboss.org">Hiram Chirino</a> .
+ * @author <a href="mailto:jason at planet57.com">Jason Dillon</a>
+ * @author <a href="mailto:adrian at jboss.com">Adrian Brock</a>
+ * @version   $Revision$
+ */
+public interface ServerSessionPoolFactory
+{
+   /**
+    * Set the name of the factory.
+    *
+    * @param name  The name of the factory.
+    */
+   void setName(String name);
+
+   /**
+    * Get the name of the factory.
+    *
+    * @return   The name of the factory.
+    */
+   String getName();
+
+   /**
+    * The <code>setXidFactory</code> method supplies the XidFactory that 
+    * server sessions will use to get Xids to control local transactions.
+    *
+    * @param xidFactory a <code>XidFactoryMBean</code> value
+    */
+   void setXidFactory(XidFactoryMBean xidFactory);
+
+   /**
+    * The <code>getXidFactory</code> method returns the XidFactory that 
+    * server sessions will use to get xids..
+    *
+    * @return a <code>XidFactoryMBean</code> value
+    */
+   XidFactoryMBean getXidFactory();
+
+   /**
+    * Create a new <tt>ServerSessionPool</tt>.
+    * 
+    * @param destination the destination
+    * @param con the jms connection
+    * @param minSession the minimum number of sessions
+    * @param maxSession the maximum number of sessions
+    * @param keepAlive the time to keep sessions alive
+    * @param isTransacted whether the pool is transacted
+    * @param ack the acknowledegement method
+    * @param listener the listener
+    * @param useLocalTX whether to use local transactions
+    * @return A new pool.
+    * @throws JMSException for any error
+    */
+   ServerSessionPool getServerSessionPool(Destination destination, Connection con, int minSession, int maxSession,
+         long keepAlive, boolean isTransacted, int ack, boolean useLocalTX, MessageListener listener)
+         throws JMSException;
+}
\ No newline at end of file

Copied: trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolLoader.java (from rev 65808, trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolLoader.java)
===================================================================
--- trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolLoader.java	                        (rev 0)
+++ trunk/server/src/main/org/jboss/jms/asf/ServerSessionPoolLoader.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,138 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.jms.asf;
+
+import javax.management.ObjectName;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.jboss.util.naming.NonSerializableFactory;
+import org.jboss.system.ServiceMBeanSupport;
+import org.jboss.tm.XidFactoryMBean;
+
+/**
+ * A loader for <tt>ServerSessionPools</tt>.
+ *
+ * @author <a href="mailto:peter.antman at tim.se">Peter Antman</a>.
+ * @author <a href="mailto:jason at planet57.com">Jason Dillon</a>
+ * @author <a href="mailto:adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision$
+ */
+public class ServerSessionPoolLoader extends ServiceMBeanSupport implements ServerSessionPoolLoaderMBean
+{
+   /** The factory used to create server session pools. */
+   private ServerSessionPoolFactory poolFactory;
+
+   /** The name of the pool. */
+   private String name;
+
+   /** The type of pool factory to use. */
+   private String poolFactoryClass;
+
+   private ObjectName xidFactory;
+
+   public void setPoolName(final String name)
+   {
+      this.name = name;
+   }
+
+   public String getPoolName()
+   {
+      return name;
+   }
+
+   public void setPoolFactoryClass(final String classname)
+   {
+      this.poolFactoryClass = classname;
+   }
+
+   public String getPoolFactoryClass()
+   {
+      return poolFactoryClass;
+   }
+
+   public ObjectName getXidFactory()
+   {
+      return xidFactory;
+   }
+
+   public void setXidFactory(final ObjectName xidFactory)
+   {
+      this.xidFactory = xidFactory;
+   }
+
+   protected void startService() throws Exception
+   {
+      XidFactoryMBean xidFactoryObj = (XidFactoryMBean) getServer().getAttribute(xidFactory, "Instance");
+
+      Class cls = Class.forName(poolFactoryClass);
+      poolFactory = (ServerSessionPoolFactory) cls.newInstance();
+      poolFactory.setName(name);
+      poolFactory.setXidFactory(xidFactoryObj);
+      log.debug("initialized with pool factory: " + poolFactory);
+
+      InitialContext ctx = new InitialContext();
+      String name = poolFactory.getName();
+      String jndiname = "java:/" + name;
+      try
+      {
+         NonSerializableFactory.rebind(ctx, jndiname, poolFactory);
+         log.debug("pool factory " + name + " bound to " + jndiname);
+      }
+      finally
+      {
+         ctx.close();
+      }
+   }
+
+   protected void stopService()
+   {
+      // Unbind from JNDI
+      InitialContext ctx = null;
+      try
+      {
+         ctx = new InitialContext();
+         String name = poolFactory.getName();
+         String jndiname = "java:/" + name;
+
+         ctx.unbind(jndiname);
+         NonSerializableFactory.unbind(jndiname);
+         log.debug("pool factory " + name + " unbound from " + jndiname);
+      }
+      catch (NamingException ignore)
+      {
+      }
+      finally
+      {
+         if (ctx != null)
+         {
+            try
+            {
+               ctx.close();
+            }
+            catch (NamingException ignore)
+            {
+            }
+         }
+      }
+   }
+}

Copied: trunk/server/src/main/org/jboss/jms/asf/StdServerSession.java (from rev 65808, trunk/server/src/main/org/jboss/jms/asf/StdServerSession.java)
===================================================================
--- trunk/server/src/main/org/jboss/jms/asf/StdServerSession.java	                        (rev 0)
+++ trunk/server/src/main/org/jboss/jms/asf/StdServerSession.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,520 @@
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2005, JBoss Inc., and individual contributors as indicated
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* This is free software; you can redistribute it and/or modify it
+* under the terms of the GNU Lesser General Public License as
+* published by the Free Software Foundation; either version 2.1 of
+* the License, or (at your option) any later version.
+*
+* This software is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this software; if not, write to the Free
+* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+*/
+package org.jboss.jms.asf;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.ServerSession;
+import javax.jms.Session;
+import javax.jms.XASession;
+import javax.naming.InitialContext;
+import javax.transaction.Status;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+import org.jboss.logging.Logger;
+import org.jboss.tm.TransactionManagerService;
+import org.jboss.tm.XidFactoryMBean;
+
+/**
+ * An implementation of ServerSession. <p>
+ *
+ * @author <a href="mailto:peter.antman at tim.se">Peter Antman</a> .
+ * @author <a href="mailto:jason at planet57.com">Jason Dillon</a>
+ * @author <a href="mailto:hiram.chirino at jboss.org">Hiram Chirino</a> .
+ * @author <a href="mailto:adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision$
+ */
+public class StdServerSession implements Runnable, ServerSession, MessageListener
+{
+   /** Instance logger. */
+   static Logger log = Logger.getLogger(StdServerSession.class);
+
+   /** The server session pool which we belong to. */
+   private StdServerSessionPool serverSessionPool;
+
+   /** Our session resource. */
+   private Session session;
+
+   /** Our XA session resource. */
+   private XASession xaSession;
+
+   /** The transaction manager that we will use for transactions. */
+   private TransactionManager tm;
+
+   /**
+    * Use the session's XAResource directly if we have an JBossMQ XASession.
+    * this allows us to get around the TX timeout problem when you have
+    * extensive message processing.
+    */
+   private boolean useLocalTX;
+
+   /** The listener to delegate calls, to. In our case the container invoker. */
+   private MessageListener delegateListener;
+
+   private XidFactoryMBean xidFactory;
+
+   /**
+    * @deprecated 
+    * @todo these appeared in jboss-head where are they used?
+    */
+   public TransactionManager getTransactionManager()
+   {
+      return tm;
+   }
+
+   /**
+    * @deprecated 
+    * @todo these appeared in jboss-head where are they used?
+    */
+   public void setTransactionManager(TransactionManager transactionManager)
+   {
+      this.tm = transactionManager;
+   }
+
+   /**
+    * Create a <tt>StdServerSession</tt> .
+    *
+    * @param pool             The server session pool which we belong to.
+    * @param session          Our session resource.
+    * @param xaSession        Our XA session resource.
+    * @param delegateListener Listener to call when messages arrives.
+    * @param useLocalTX       Will this session be used in a global TX (we can optimize with 1 phase commit)
+    * @throws JMSException Transation manager was not found.
+    */
+   StdServerSession(final StdServerSessionPool pool,
+                    final Session session,
+                    final XASession xaSession,
+                    final MessageListener delegateListener,
+                    boolean useLocalTX,
+                    final XidFactoryMBean xidFactory,
+                    final TransactionManager tm)
+           throws JMSException
+   {
+      this.serverSessionPool = pool;
+      this.session = session;
+      this.xaSession = xaSession;
+      this.delegateListener = delegateListener;
+      if (xaSession == null)
+         useLocalTX = false;
+      this.useLocalTX = useLocalTX;
+      this.xidFactory = xidFactory;
+      this.tm = tm;
+
+      log.trace(this + " initializing (pool, session, xaSession, useLocalTX): " +
+                pool + ", " + session + ", " + xaSession + ", " + useLocalTX);
+
+      // Set out self as message listener
+      if (StdServerSessionPoolFactory.USE_OLD && xaSession != null)
+         xaSession.setMessageListener(this);
+      else
+         session.setMessageListener(this);
+
+      if (tm == null)
+      {
+         InitialContext ctx = null;
+         try
+         {
+            ctx = new InitialContext();
+            this.tm = (TransactionManager) ctx.lookup(TransactionManagerService.JNDI_NAME);
+         }
+         catch (Exception e)
+         {
+            throw new JMSException("Transation manager was not found");
+         }
+         finally
+         {
+            if (ctx != null)
+            {
+               try
+               {
+                  ctx.close();
+               }
+               catch (Exception ignore)
+               {
+               }
+            }
+         }
+      }
+   }
+
+   /**
+    * Returns the session. <p>
+    * <p/>
+    * This simply returns what it has fetched from the connection. It is up to
+    * the jms provider to typecast it and have a private API to stuff messages
+    * into it.
+    *
+    * @return The session.
+    * @throws JMSException Description of Exception
+    */
+   public Session getSession() throws JMSException
+   {
+      if (StdServerSessionPoolFactory.USE_OLD && xaSession != null)
+         return xaSession;
+      else
+         return session;
+   }
+
+   /**
+    * Runs in an own thread, basically calls the session.run(), it is up to the
+    * session to have been filled with messages and it will run against the
+    * listener set in StdServerSessionPool. When it has send all its messages it
+    * returns.
+    */
+   public void run()
+   {
+      boolean trace = log.isTraceEnabled();
+
+      TransactionDemarcation td = null;
+      if (StdServerSessionPoolFactory.USE_OLD == false)
+      {
+         td = createTransactionDemarcation();
+         if (td == null)
+            return;
+      }
+      try
+      {
+         if (trace)
+            log.trace(this + " running...");
+
+         if (StdServerSessionPoolFactory.USE_OLD && xaSession != null)
+            xaSession.run();
+         else
+            session.run();
+
+         if (trace)
+            log.trace(this + " run.");
+      }
+      catch (Throwable t)
+      {
+         log.error(this + " onMessage failed to run; setting rollback only", t);
+         if (td != null)
+            td.error();
+      }
+      finally
+      {
+         if (td != null)
+            td.end();
+
+         recycle();
+      }
+   }
+
+   /**
+    * Will get called from session for each message stuffed into it.
+    * <p/>
+    * Starts a transaction with the TransactionManager
+    * and enlists the XAResource of the JMS XASession if a XASession was
+    * available. A good JMS implementation should provide the XASession for use
+    * in the ASF. So we optimize for the case where we have an XASession. So,
+    * for the case where we do not have an XASession and the bean is not
+    * transacted, we have the unneeded overhead of creating a Transaction. I'm
+    * leaving it this way since it keeps the code simpler and that case should
+    * not be too common (JBossMQ provides XASessions).
+    */
+   public void onMessage(Message msg)
+   {
+      boolean trace = log.isTraceEnabled();
+
+      TransactionDemarcation td = null;
+      if (StdServerSessionPoolFactory.USE_OLD)
+      {
+         td = createTransactionDemarcation();
+         if (td == null)
+            return;
+      }
+      try
+      {
+         if (trace)
+            log.trace(this + " onMessage running (pool, session, xaSession, useLocalTX): " +
+                      ", " + session + ", " + xaSession + ", " + useLocalTX);
+
+         // Call delegate listener
+         delegateListener.onMessage(msg);
+
+         if (trace)
+            log.trace(this + " onMessage finished");
+      }
+      catch (Throwable t)
+      {
+         log.error(this + " onMessage failed to run; setting rollback only", t);
+         if (td != null)
+            td.error();
+      }
+      finally
+      {
+         if (td != null)
+            td.end();
+      }
+      if (trace)
+         log.trace(this + " onMessage done");
+   }
+
+   /**
+    * Start the session and begin consuming messages.
+    *
+    * @throws JMSException No listener has been specified.
+    */
+   public void start() throws JMSException
+   {
+      log.trace(this + " starting invokes on server session");
+
+      if (session != null)
+      {
+         try
+         {
+            serverSessionPool.getExecutor().execute(this);
+         }
+         catch (InterruptedException ignore)
+         {
+         }
+      }
+      else
+      {
+         throw new JMSException(this + " no listener has been specified");
+      }
+   }
+
+   /**
+    * Called by the ServerSessionPool when the sessions should be closed.
+    */
+   void close()
+   {
+      log.trace(this + " closing.");
+      
+      if (session != null)
+      {
+         try
+         {
+            session.close();
+         }
+         catch (Exception ignore)
+         {
+         }
+
+         session = null;
+      }
+
+      if (xaSession != null)
+      {
+         try
+         {
+            xaSession.close();
+         }
+         catch (Exception ignore)
+         {
+         }
+         xaSession = null;
+      }
+
+      log.debug("closed");
+   }
+
+   /**
+    * This method is called by the ServerSessionPool when it is ready to be
+    * recycled intot the pool
+    */
+   void recycle()
+   {
+      boolean trace = log.isTraceEnabled();
+      if (trace)
+         log.trace(this + " recycling");
+      serverSessionPool.recycle(this);
+      if (trace)
+         log.trace(this + " recycled");
+   }
+   
+   TransactionDemarcation createTransactionDemarcation()
+   {
+      try
+      {
+         return new TransactionDemarcation();
+      }
+      catch (Throwable t)
+      {
+         log.error(this + " error creating transaction demarcation ", t);
+         return null;
+      }
+   }
+   
+   private class TransactionDemarcation
+   {
+      boolean trace = log.isTraceEnabled();
+      
+      // Used if run with useLocalTX if true
+      Xid localXid = null;
+      boolean localRollbackFlag = false;
+      // Used if run with useLocalTX if false
+      Transaction trans = null;
+
+      public TransactionDemarcation() throws Throwable
+      {
+         if (useLocalTX)
+         {
+            // Use JBossMQ One Phase Commit to commit the TX
+            localXid = xidFactory.newXid();//new XidImpl();
+            XAResource res = xaSession.getXAResource();
+            res.start(localXid, XAResource.TMNOFLAGS);
+
+            if (trace)
+               log.trace(StdServerSession.this + " using optimized 1p commit to control TX. xid=" + localXid);
+         }
+         else
+         {
+
+            // Use the TM to control the TX
+            tm.begin();
+            try
+            {
+               trans = tm.getTransaction();
+
+               if (trace)
+                  log.trace(StdServerSession.this +  " using tx=" + trans);
+
+               if (xaSession != null)
+               {
+                  XAResource res = xaSession.getXAResource();
+                  if (!trans.enlistResource(res))
+                  {
+                     throw new JMSException("could not enlist resource");
+                  }
+                  if (trace)
+                     log.trace(StdServerSession.this + " XAResource '" + res + "' enlisted.");
+               }
+            }
+            catch (Throwable t)
+            {
+               try
+               {
+                  tm.rollback();
+               }
+               catch (Throwable ignored)
+               {
+                  log.trace(StdServerSession.this + " ignored error rolling back after failed enlist", ignored);
+               }
+               throw t;
+            }
+         }
+      }
+      
+      public void error()
+      {
+         if (useLocalTX)
+         {
+            // Use JBossMQ One Phase Commit to commit the TX
+            localRollbackFlag = true;
+         }
+         else
+         {
+            // Mark for tollback TX via TM
+            try
+            {
+               // The transaction will be rolledback in the finally
+               if (trace)
+                  log.trace(StdServerSession.this + " using TM to mark TX for rollback tx=" + trans);
+               trans.setRollbackOnly();
+            }
+            catch (Throwable t)
+            {
+               log.error(StdServerSession.this + " failed to set rollback only", t);
+            }
+         }
+      }
+      
+      public void end()
+      {
+         try
+         {
+            if (useLocalTX)
+            {
+               if (localRollbackFlag == true)
+               {
+                  if (trace)
+                     log.trace(StdServerSession.this + " using optimized 1p commit to rollback TX xid=" + localXid);
+
+                  XAResource res = xaSession.getXAResource();
+                  res.end(localXid, XAResource.TMSUCCESS);
+                  res.rollback(localXid);
+
+               }
+               else
+               {
+                  if (trace)
+                     log.trace(StdServerSession.this +  " using optimized 1p commit to commit TX xid=" + localXid);
+
+                  XAResource res = xaSession.getXAResource();
+                  res.end(localXid, XAResource.TMSUCCESS);
+                  res.commit(localXid, true);
+               }
+            }
+            else
+            {
+               // Use the TM to commit the Tx (assert the correct association) 
+               Transaction currentTx = tm.getTransaction();
+               if (trans.equals(currentTx) == false)
+                  throw new IllegalStateException("Wrong tx association: expected " + trans + " was " + currentTx);
+
+               // Marked rollback
+               if (trans.getStatus() == Status.STATUS_MARKED_ROLLBACK)
+               {
+                  if (trace)
+                     log.trace(StdServerSession.this + " rolling back JMS transaction tx=" + trans);
+                  // actually roll it back
+                  tm.rollback();
+
+                  // NO XASession? then manually rollback.
+                  // This is not so good but
+                  // it's the best we can do if we have no XASession.
+                  if (xaSession == null && serverSessionPool.isTransacted())
+                  {
+                     session.rollback();
+                  }
+               }
+               else if (trans.getStatus() == Status.STATUS_ACTIVE)
+               {
+                  // Commit tx
+                  // This will happen if
+                  // a) everything goes well
+                  // b) app. exception was thrown
+                  if (trace)
+                     log.trace(StdServerSession.this + " commiting the JMS transaction tx=" + trans);
+                  tm.commit();
+
+                  // NO XASession? then manually commit.  This is not so good but
+                  // it's the best we can do if we have no XASession.
+                  if (xaSession == null && serverSessionPool.isTransacted())
+                  {
+                     session.commit();
+                  }
+               }
+            }
+         }
+         catch (Throwable t)
+         {
+            log.error(StdServerSession.this + " failed to commit/rollback", t);
+         }
+      }
+   }
+}

Copied: trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPool.java (from rev 65808, trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPool.java)
===================================================================
--- trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPool.java	                        (rev 0)
+++ trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPool.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,413 @@
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2005, JBoss Inc., and individual contributors as indicated
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* This is free software; you can redistribute it and/or modify it
+* under the terms of the GNU Lesser General Public License as
+* published by the Free Software Foundation; either version 2.1 of
+* the License, or (at your option) any later version.
+*
+* This software is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this software; if not, write to the Free
+* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+*/
+package org.jboss.jms.asf;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MessageListener;
+import javax.jms.Queue;
+import javax.jms.QueueConnection;
+import javax.jms.ServerSession;
+import javax.jms.ServerSessionPool;
+import javax.jms.Session;
+import javax.jms.Topic;
+import javax.jms.TopicConnection;
+import javax.jms.XAQueueConnection;
+import javax.jms.XAQueueSession;
+import javax.jms.XASession;
+import javax.jms.XATopicConnection;
+import javax.jms.XATopicSession;
+import javax.transaction.TransactionManager;
+
+import org.jboss.logging.Logger;
+import org.jboss.tm.XidFactoryMBean;
+
+import EDU.oswego.cs.dl.util.concurrent.Executor;
+import EDU.oswego.cs.dl.util.concurrent.PooledExecutor;
+import EDU.oswego.cs.dl.util.concurrent.ThreadFactory;
+
+/**
+ * Implementation of ServerSessionPool.
+ *
+ * @author    <a href="mailto:peter.antman at tim.se">Peter Antman</a> .
+ * @author    <a href="mailto:hiram.chirino at jboss.org">Hiram Chirino</a> .
+ * @author <a href="mailto:adrian at jboss.com">Adrian Brock</a>
+ * @version   $Revision$
+ */
+public class StdServerSessionPool implements ServerSessionPool
+{
+   /** The thread group which session workers will run. */
+   private static ThreadGroup threadGroup = new ThreadGroup("ASF Session Pool Threads");
+
+   /** Instance logger. */
+   private final Logger log = Logger.getLogger(this.getClass());
+
+   /** The minimum size of the pool */
+   private int minSize;
+
+   /** The size of the pool. */
+   private int poolSize;
+
+   /** The message acknowledgment mode. */
+   private int ack;
+
+   /** Is the bean container managed? */
+   private boolean useLocalTX;
+
+   /** True if this is a transacted session. */
+   private boolean transacted;
+
+   /** The destination. */
+   private Destination destination;
+
+   /** The session connection. */
+   private Connection con;
+
+   /** The message listener for the session. */
+   private MessageListener listener;
+
+   /** The list of ServerSessions. */
+   private List sessionPool;
+
+   /** The executor for processing messages? */
+   private PooledExecutor executor;
+
+   /** Used to signal when the Pool is being closed down */
+   private boolean closing = false;
+
+   /** Used during close down to wait for all server sessions to be returned and closed. */
+   private int numServerSessions = 0;
+
+   private XidFactoryMBean xidFactory;
+
+   private TransactionManager tm;
+
+   /**
+    * Construct a <tt>StdServerSessionPool</tt> using the default pool size.
+    *
+    * @param destination the destination
+    * @param con connection to get sessions from
+    * @param transacted transaction mode when not XA (
+    * @param ack ackmode when not XA
+    * @param listener the listener the sessions will call
+    * @param minSession minumum number of sessions in the pool
+    * @param maxSession maximum number of sessions in the pool
+    * @param keepAlive the time to keep sessions alive
+    * @param xidFactory  the xid factory
+    * @param tm the transaction manager
+    * @exception JMSException    Description of Exception
+    */
+   public StdServerSessionPool(final Destination destination,
+                               final Connection con,
+                               final boolean transacted,
+                               final int ack,
+                               final boolean useLocalTX,
+                               final MessageListener listener,
+                               final int minSession,
+                               final int maxSession,
+                               final long keepAlive,
+                               final XidFactoryMBean xidFactory,
+                               final TransactionManager tm)
+      throws JMSException
+   {
+      this.destination = destination;
+      this.con = con;
+      this.ack = ack;
+      this.listener = listener;
+      this.transacted = transacted;
+      this.minSize = minSession;
+      this.poolSize = maxSession;
+      this.sessionPool = new ArrayList(maxSession);
+      this.useLocalTX = useLocalTX;
+      this.xidFactory = xidFactory;
+      this.tm = tm;
+      // setup the worker pool
+      executor = new MyPooledExecutor(poolSize);
+      executor.setMinimumPoolSize(minSize);
+      executor.setKeepAliveTime(keepAlive);
+      executor.waitWhenBlocked();
+      executor.setThreadFactory(new DefaultThreadFactory());
+
+      // finish initializing the session
+      create();
+      log.debug("Server Session pool set up");
+   }
+
+   /**
+    * Get a server session.
+    *
+    * @return               A server session.
+    * @throws JMSException  Failed to get a server session.
+    */
+   public ServerSession getServerSession() throws JMSException
+   {
+      if( log.isTraceEnabled() )
+         log.trace("getting a server session");
+      ServerSession session = null;
+
+      try
+      {
+         while (true)
+         {
+            synchronized (sessionPool)
+            {
+               if (closing)
+               {
+                  throw new JMSException("Cannot get session after pool has been closed down.");
+               }
+               else if (sessionPool.size() > 0)
+               {
+                  session = (ServerSession)sessionPool.remove(0);
+                  break;
+               }
+               else
+               {
+                  try
+                  {
+                     sessionPool.wait();
+                  }
+                  catch (InterruptedException ignore)
+                  {
+                  }
+               }
+            }
+         }
+      }
+      catch (Exception e)
+      {
+         throw new JMSException("Failed to get a server session: " + e);
+      }
+
+      if( log.isTraceEnabled() )
+         log.trace("using server session: " + session);
+      return session;
+   }
+
+   /**
+    * Clear the pool, clear out both threads and ServerSessions,
+    * connection.stop() should be run before this method.
+    */
+   public void clear()
+   {
+      synchronized (sessionPool)
+      {
+         // FIXME - is there a runaway condition here. What if a
+         // ServerSession are taken by a ConnecionConsumer? Should we set
+         // a flag somehow so that no ServerSessions are recycled and the
+         // ThreadPool won't leave any more threads out.
+         closing = true;
+
+         log.debug("Clearing " + sessionPool.size() + " from ServerSessionPool");
+
+         Iterator iter = sessionPool.iterator();
+         while (iter.hasNext())
+         {
+            StdServerSession ses = (StdServerSession)iter.next();
+            // Should we do anything to the server session?
+            ses.close();
+            numServerSessions--;
+         }
+
+         sessionPool.clear();
+         sessionPool.notifyAll();
+      }
+
+      //Must be outside synchronized block because of recycle method.
+      executor.shutdownAfterProcessingCurrentlyQueuedTasks();
+
+      //wait for all server sessions to be returned.
+      synchronized (sessionPool)
+      {
+         while (numServerSessions > 0)
+         {
+            try
+            {
+               sessionPool.wait();
+            }
+            catch (InterruptedException ignore)
+            {
+            }
+         }
+      }
+   }
+
+   /**
+    * Get the executor we are using.
+    *
+    * @return   The Executor value
+    */
+   Executor getExecutor()
+   {
+      return executor;
+   }
+
+   // --- Protected messages for StdServerSession to use
+
+   /**
+    * Returns true if this server session is transacted.
+    *
+    * @return   The Transacted value
+    */
+   boolean isTransacted()
+   {
+      return transacted;
+   }
+
+   /**
+    * Recycle a server session.
+    *
+    * @param session  Description of Parameter
+    */
+   void recycle(StdServerSession session)
+   {
+      synchronized (sessionPool)
+      {
+         if (closing)
+         {
+            session.close();
+            numServerSessions--;
+            if (numServerSessions == 0)
+            {
+               //notify clear thread.
+               sessionPool.notifyAll();
+            }
+         }
+         else
+         {
+            sessionPool.add(session);
+            sessionPool.notifyAll();
+            if( log.isTraceEnabled() )
+               log.trace("recycled server session: " + session);
+         }
+      }
+   }
+
+   private void create() throws JMSException
+   {
+      for (int index = 0; index < poolSize; index++)
+      {
+         // Here is the meat, that MUST follow the spec
+         Session ses = null;
+         XASession xaSes = null;
+
+         log.debug("initializing with connection: " + con);
+
+         if (destination instanceof Topic && con instanceof XATopicConnection)
+         {
+            xaSes = ((XATopicConnection)con).createXATopicSession();
+            ses = ((XATopicSession)xaSes).getTopicSession();
+         }
+         else if (destination instanceof Queue && con instanceof XAQueueConnection)
+         {
+            xaSes = ((XAQueueConnection)con).createXAQueueSession();
+            ses = ((XAQueueSession)xaSes).getQueueSession();
+         }
+         else if (destination instanceof Topic && con instanceof TopicConnection)
+         {
+            ses = ((TopicConnection)con).createTopicSession(transacted, ack);
+            log.warn("Using a non-XA TopicConnection.  " +
+                  "It will not be able to participate in a Global UOW");
+         }
+         else if (destination instanceof Queue && con instanceof QueueConnection)
+         {
+            ses = ((QueueConnection)con).createQueueSession(transacted, ack);
+            log.warn("Using a non-XA QueueConnection.  " +
+                  "It will not be able to participate in a Global UOW");
+         }
+         else
+         {
+            throw new JMSException("Connection was not reconizable: " + con + " for destination " + destination);
+         }
+
+         // create the server session and add it to the pool - it is up to the
+         // server session to set the listener
+         StdServerSession serverSession = new StdServerSession(this, ses, xaSes,
+            listener, useLocalTX, xidFactory, tm);
+
+         sessionPool.add(serverSession);
+         numServerSessions++;
+
+         log.debug("added server session to the pool: " + serverSession);
+      }
+   }
+
+   /**
+    * A pooled executor where the minimum pool size
+    * threads are kept alive
+    */
+   private static class MyPooledExecutor extends PooledExecutor
+   {
+      public MyPooledExecutor(int poolSize)
+      {
+         super(poolSize);
+      }
+      
+      protected Runnable getTask() throws InterruptedException
+      {
+         Runnable task = null;
+         while ((task = super.getTask()) == null && keepRunning());
+         return task;
+      }
+      
+      /**
+       * We keep running unless we are told to shutdown
+       * or there are more than minimumPoolSize_ threads in the pool
+       * 
+       * @return whether to keep running
+       */
+      protected synchronized boolean keepRunning()
+      {
+         if (shutdown_)
+            return false;
+         
+         return poolSize_ <= minimumPoolSize_;
+      }
+   }
+
+   private static class DefaultThreadFactory implements ThreadFactory
+   {
+      private static int count = 0;
+      private static synchronized int nextCount()
+      {
+         return count ++;
+      }
+
+      /**
+       * Create a new Thread for the given Runnable
+       *
+       * @param command The Runnable to pass to Thread
+       * @return The newly created Thread
+       */
+      public Thread newThread(final Runnable command)
+      {
+         String name = "JMS SessionPool Worker-" + nextCount();
+         Thread thread = new Thread(threadGroup, command, name);
+         thread.setDaemon(true);
+         return thread;
+      }
+   }
+}

Copied: trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPoolFactory.java (from rev 65808, trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPoolFactory.java)
===================================================================
--- trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPoolFactory.java	                        (rev 0)
+++ trunk/server/src/main/org/jboss/jms/asf/StdServerSessionPoolFactory.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,109 @@
+/*
+* JBoss, Home of Professional Open Source
+* Copyright 2005, JBoss Inc., and individual contributors as indicated
+* by the @authors tag. See the copyright.txt in the distribution for a
+* full listing of individual contributors.
+*
+* This is free software; you can redistribute it and/or modify it
+* under the terms of the GNU Lesser General Public License as
+* published by the Free Software Foundation; either version 2.1 of
+* the License, or (at your option) any later version.
+*
+* This software is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+* Lesser General Public License for more details.
+*
+* You should have received a copy of the GNU Lesser General Public
+* License along with this software; if not, write to the Free
+* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+*/
+package org.jboss.jms.asf;
+
+import java.io.Serializable;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MessageListener;
+import javax.jms.ServerSessionPool;
+import javax.transaction.TransactionManager;
+
+import org.jboss.tm.XidFactoryMBean;
+
+/**
+ * An implementation of ServerSessionPoolFactory. 
+ *
+ * @author    <a href="mailto:peter.antman at tim.se">Peter Antman</a> .
+ * @author    <a href="mailto:hiram.chirino at jboss.org">Hiram Chirino</a> .
+ * @author <a href="mailto:adrian at jboss.com">Adrian Brock</a>
+ * @version   $Revision$
+ */
+public class StdServerSessionPoolFactory implements ServerSessionPoolFactory, Serializable
+{
+   private static final long serialVersionUID = 4969432475779524576L;
+
+   public static final boolean USE_OLD;
+   
+   static
+   {
+      USE_OLD = ((Boolean) AccessController.doPrivileged(new PrivilegedAction()
+      {
+         public Object run()
+         {
+            return new Boolean(System.getProperty("org.jboss.jms.asf.useold", "false"));
+         }
+      })).booleanValue();
+   }
+   
+   /** The name of this factory. */
+   private String name;
+
+   private XidFactoryMBean xidFactory;
+
+   private TransactionManager transactionManager;
+
+   public StdServerSessionPoolFactory()
+   {
+      super();
+   }
+
+   public void setName(final String name)
+   {
+      this.name = name;
+   }
+
+   public String getName()
+   {
+      return name;
+   }
+
+   public void setXidFactory(final XidFactoryMBean xidFactory)
+   {
+      this.xidFactory = xidFactory;
+   }
+
+   public XidFactoryMBean getXidFactory()
+   {
+      return xidFactory;
+   }
+
+   public void setTransactionManager(TransactionManager transactionManager)
+   {
+      this.transactionManager = transactionManager;
+   }
+
+   public TransactionManager getTransactionManager()
+   {
+      return transactionManager;
+   }
+
+   public ServerSessionPool getServerSessionPool(Destination destination, Connection con, int minSession, int maxSession, long keepAlive, boolean isTransacted, int ack, boolean useLocalTX, MessageListener listener) throws JMSException
+   {
+      ServerSessionPool pool = new StdServerSessionPool(destination, con, isTransacted, ack, useLocalTX, listener, minSession, maxSession, keepAlive, xidFactory, transactionManager);
+      return pool;
+   }
+}

Added: trunk/transaction/build.bat
===================================================================
--- trunk/transaction/build.bat	                        (rev 0)
+++ trunk/transaction/build.bat	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,89 @@
+ at echo off
+REM  ======================================================================
+REM
+REM  This is the main entry point for the build system.
+REM
+REM  Users should be sure to execute this file rather than 'ant' to ensure
+REM  the correct version is being used with the correct configuration.
+REM
+REM  ======================================================================
+REM
+REM $Id: build.bat 24242 2004-10-04 20:13:37Z andd $
+REM
+REM Authors:
+REM     Jason Dillon <jason at planet57.com>
+REM     Sacha Labourey <sacha.labourey at cogito-info.ch>
+REM
+
+REM ******************************************************
+REM Ignore the ANT_HOME variable: we want to use *our*
+REM ANT version and associated JARs.
+REM ******************************************************
+REM Ignore the users classpath, cause it might mess
+REM things up
+REM ******************************************************
+
+SETLOCAL
+
+set CLASSPATH=
+set ANT_HOME=
+set ANT_OPTS=-Djava.protocol.handler.pkgs=org.jboss.net.protocol -Dbuild.script=build.bat
+
+REM ******************************************************
+REM - "for" loops have been unrolled for compatibility
+REM   with some WIN32 systems.
+REM ******************************************************
+
+set NAMES=tools;tools\ant;tools\apache\ant
+set SUBFOLDERS=..;..\..;..\..\..;..\..\..\..
+
+REM ******************************************************
+REM ******************************************************
+
+SET EXECUTED=FALSE
+for %%i in (%NAMES%) do call :subLoop %%i %1 %2 %3 %4 %5 %6
+
+goto :EOF
+
+
+REM ******************************************************
+REM ********* Search for names in the subfolders *********
+REM ******************************************************
+
+:subLoop
+for %%j in (%SUBFOLDERS%) do call :testIfExists %%j\%1\bin\ant.bat %2 %3 %4 %5 %6 %7
+
+goto :EOF
+
+
+REM ******************************************************
+REM ************ Test if ANT Batch file exists ***********
+REM ******************************************************
+
+:testIfExists
+if exist %1 call :BatchFound %1 %2 %3 %4 %5 %6 %7 %8
+
+goto :EOF
+
+
+REM ******************************************************
+REM ************** Batch file has been found *************
+REM ******************************************************
+
+:BatchFound
+if (%EXECUTED%)==(FALSE) call :ExecuteBatch %1 %2 %3 %4 %5 %6 %7 %8
+set EXECUTED=TRUE
+
+goto :EOF
+
+REM ******************************************************
+REM ************* Execute Batch file only once ***********
+REM ******************************************************
+
+:ExecuteBatch
+echo Calling %1 %2 %3 %4 %5 %6 %7 %8
+call %1 %2 %3 %4 %5 %6 %7 %8
+
+:end
+
+if "%NOPAUSE%" == "" pause


Property changes on: trunk/transaction/build.bat
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/transaction/build.sh
===================================================================
--- trunk/transaction/build.sh	                        (rev 0)
+++ trunk/transaction/build.sh	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,172 @@
+#!/bin/sh
+### ====================================================================== ###
+##                                                                          ##
+##  This is the main entry point for the build system.                      ##
+##                                                                          ##
+##  Users should be sure to execute this file rather than 'ant' to ensure   ##
+##  the correct version is being used with the correct configuration.       ##
+##                                                                          ##
+### ====================================================================== ###
+
+# $Id: build.sh 24242 2004-10-04 20:13:37Z andd $
+
+PROGNAME=`basename $0`
+DIRNAME=`dirname $0`
+GREP="grep"
+ROOT="/"
+
+# Ignore user's ANT_HOME if it is set
+ANT_HOME=""
+
+# the default search path for ant
+ANT_SEARCH_PATH="\
+    tools
+    tools/ant \
+    tools/apache/ant \
+    ant"
+
+# the default build file name
+ANT_BUILD_FILE="build.xml"
+
+# the default arguments
+ANT_OPTIONS="-find $ANT_BUILD_FILE"
+
+# Use the maximum available, or set MAX_FD != -1 to use that
+MAX_FD="maximum"
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false;
+darwin=false;
+case "`uname`" in
+    CYGWIN*)
+        cygwin=true
+        ;;
+
+    Darwin*)
+        darwin=true
+        ;;
+esac
+
+#
+# Helper to complain.
+#
+die() {
+    echo "${PROGNAME}: $*"
+    exit 1
+}
+
+#
+# Helper to complain.
+#
+warn() {
+    echo "${PROGNAME}: $*"
+}
+
+#
+# Helper to source a file if it exists.
+#
+maybe_source() {
+    for file in $*; do
+	if [ -f "$file" ]; then
+	    . $file
+	fi
+    done
+}
+
+search() {
+    search="$*"
+    for d in $search; do
+	ANT_HOME="`pwd`/$d"
+	ANT="$ANT_HOME/bin/ant"
+	if [ -x "$ANT" ]; then
+	    # found one
+	    echo $ANT_HOME
+	    break
+	fi
+    done
+}
+
+#
+# Main function.
+#
+main() {
+    # if there is a build config file. then source it
+    maybe_source "$DIRNAME/build.conf" "$HOME/.build.conf"
+
+    # Increase the maximum file descriptors if we can
+    if [ $cygwin = "false" ]; then
+	MAX_FD_LIMIT=`ulimit -H -n`
+	if [ $? -eq 0 ]; then
+	    if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then
+		# use the system max
+		MAX_FD="$MAX_FD_LIMIT"
+	    fi
+
+	    ulimit -n $MAX_FD
+	    if [ $? -ne 0 ]; then
+		warn "Could not set maximum file descriptor limit: $MAX_FD"
+	    fi
+	else
+	    warn "Could not query system maximum file descriptor limit: $MAX_FD_LIMIT"
+	fi
+    fi
+
+    # try the search path
+    ANT_HOME=`search $ANT_SEARCH_PATH`
+
+    # try looking up to root
+    if [ "x$ANT_HOME" = "x" ]; then
+	target="build"
+	_cwd=`pwd`
+
+	while [ "x$ANT_HOME" = "x" ] && [ "$cwd" != "$ROOT" ]; do
+	    cd ..
+	    cwd=`pwd`
+	    ANT_HOME=`search $ANT_SEARCH_PATH`
+	done
+
+	# make sure we get back
+	cd $_cwd
+
+	if [ "$cwd" != "$ROOT" ]; then
+	    found="true"
+	fi
+
+	# complain if we did not find anything
+	if [ "$found" != "true" ]; then
+	    die "Could not locate Ant; check \$ANT or \$ANT_HOME."
+	fi
+    fi
+
+    # make sure we have one
+    ANT=$ANT_HOME/bin/ant
+    if [ ! -x "$ANT" ]; then
+	die "Ant file is not executable: $ANT"
+    fi
+
+    # need to specify planet57/buildmagic protocol handler package
+    ANT_OPTS="-Djava.protocol.handler.pkgs=org.jboss.net.protocol"
+
+    # setup some build properties
+    ANT_OPTS="$ANT_OPTS -Dbuild.script=$0"
+
+    # change to the directory where the script lives so users are not forced
+    # to be in the same directory as build.xml
+    cd $DIRNAME
+
+    # export some stuff for ant
+    export ANT ANT_HOME ANT_OPTS
+
+    # execute in debug mode, or simply execute
+    if [ "x$ANT_DEBUG" != "x" ]; then
+	/bin/sh -x $ANT $ANT_OPTIONS "$@"
+    else
+	exec $ANT $ANT_OPTIONS "$@"
+    fi
+}
+
+##
+## Bootstrap
+##
+
+main "$@"


Property changes on: trunk/transaction/build.sh
___________________________________________________________________
Name: svn:executable
   + *

Added: trunk/transaction/build.xml
===================================================================
--- trunk/transaction/build.xml	                        (rev 0)
+++ trunk/transaction/build.xml	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,333 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE project [
+   <!ENTITY buildmagic SYSTEM "../tools/etc/buildmagic/buildmagic.ent">
+   <!ENTITY libraries SYSTEM "../thirdparty/libraries.ent">
+   <!ENTITY modules SYSTEM "../tools/etc/buildmagic/modules.ent">
+]>
+
+<!-- ====================================================================== -->
+<!--                                                                        -->
+<!--  JBoss, the OpenSource J2EE webOS                                      -->
+<!--                                                                        -->
+<!--  Distributable under LGPL license.                                     -->
+<!--  See terms of license at http://www.gnu.org.                           -->
+<!--                                                                        -->
+<!-- ====================================================================== -->
+
+<!-- $Id: build.xml 64576 2007-08-14 15:48:44Z adrian at jboss.org $ -->
+
+<project default="main" name="JBoss/Transaction">
+
+  <!-- ================================================================== -->
+  <!-- Setup                                                              -->
+  <!-- ================================================================== -->
+
+  <!--
+     | Include the common Buildmagic elements.
+     |
+     | This defines several different targets, properties and paths.
+     | It also sets up the basic extention tasks amoung other things.
+   -->
+
+  &buildmagic;
+
+
+  <!-- ================================================================== -->
+  <!-- Initialization                                                     -->
+  <!-- ================================================================== -->
+
+  <!--
+     | Initialize the build system.  Must depend on '_buildmagic:init'.
+     | Other targets should depend on 'init' or things will mysteriously fail.
+   -->
+
+  <target name="init" unless="init.disable" depends="_buildmagic:init">
+  </target>
+
+
+  <!-- ================================================================== -->
+  <!-- Configuration                                                      -->
+  <!-- ================================================================== -->
+
+  <!--
+     | Configure the build system.
+     |
+     | This target is invoked by the Buildmagic initialization logic and
+     | should contain module specific configuration elements.
+   -->
+
+  <target name="configure" unless="configure.disable">
+
+    <!-- =================== -->
+    <!-- Basic Configuration -->
+    <!-- =================== -->
+
+    <!-- Module name(s) & version -->
+    <property name="module.name" value="jboss-transaction"/>
+    <property name="module.Name" value="JBoss Transaction"/>
+    <property name="module.version" value="DEV"/>
+
+    <!-- ========= -->
+    <!-- Libraries -->
+    <!-- ========= -->
+     &libraries;
+
+    <!-- The combined library classpath -->
+    <path id="library.classpath">
+      <path refid="apache.log4j.classpath"/>
+      <path refid="apache.commons.classpath"/>
+      <path refid="oswego.concurrent.classpath"/>
+      <path refid="jboss.remoting.classpath"/>
+      <path refid="jboss.integration.classpath"/>
+    </path>
+
+    <!-- ======= -->
+    <!-- Modules -->
+    <!-- ======= -->
+
+    &modules;
+
+    <!-- The combined dependent module classpath -->
+    <path id="dependentmodule.classpath">
+      <path refid="jboss.jboss.javaee.classpath"/>
+      <path refid="jboss.common.core.classpath"/>
+      <path refid="jboss.common.logging.spi.classpath"/>
+      <path refid="jboss.common.logging.log4j.classpath"/>
+      <path refid="jboss.common.logging.jdk.classpath"/>
+      <path refid="jboss.systemjmx.classpath"/>
+      <path refid="jboss.system.classpath"/>
+      <path refid="jboss.j2se.classpath"/>
+      <path refid="jboss.main.classpath"/>
+    </path>
+
+    <!-- ===== -->
+    <!-- Tasks -->
+    <!-- ===== -->
+
+    <!-- Where source files live -->
+    <property name="source.java" value="${module.source}/main"/>
+    <property name="source.etc" value="${module.source}/etc"/>
+
+    <!-- Where build generated files will go -->
+    <property name="build.reports" value="${module.output}/reports"/>
+    <property name="build.classes" value="${module.output}/classes"/>
+    <property name="build.lib" value="${module.output}/lib"/>
+    <property name="build.api" value="${module.output}/api"/>
+    <property name="build.etc" value="${module.output}/etc"/>
+    <property name="build.gen-src" value="${module.output}/gen-src"/>
+
+    <!-- Install/Release structure -->
+    <property name="install.id" value="${module.name}-${module.version}"/>
+    <property name="release.id" value="${install.id}"/>
+    <property name="install.root" value="${module.output}/${install.id}"/>
+
+    <!-- The combined thirdparty classpath -->
+    <path id="thirdparty.classpath">
+      <path refid="library.classpath"/>
+      <path refid="dependentmodule.classpath"/>
+    </path>
+
+    <!-- classpath and local.classpath must have a value using with a path -->
+    <property name="classpath" value=""/>
+    <property name="local.classpath" value=""/>
+
+    <!-- The classpath required to build classes. -->
+    <path id="javac.classpath">
+      <pathelement path="${classpath}"/>
+      <pathelement path="${local.classpath}"/>
+      <path refid="thirdparty.classpath"/>
+    </path>
+
+    <!-- The classpath required to build javadocs. -->
+    <path id="javadoc.classpath">
+      <path refid="javac.classpath"/>
+    </path>
+
+    <!-- Packages to include when generating api documentation -->
+    <property name="javadoc.packages" value="org.jboss.*"/>
+
+    <!-- Override JUnit defaults -->
+    <property name="junit.timeout" value="240000"/> <!-- 4 minutes -->
+    <property name="junit.batchtest.todir" value="${build.reports}"/>
+    <property name="junit.jvm.options" value="-Ddummy"/>
+
+    <!-- xdoclet -->
+    <path id="xdoclet.task.classpath">
+      <path refid="javac.classpath"/>
+      <fileset dir="${xdoclet.xdoclet.lib}">
+        <include name="**/*.jar"/>
+      </fileset>
+    </path>
+    <property name="xdoclet.task.classpath" refid="xdoclet.task.classpath"/>
+  </target>
+
+
+  <!-- ================================================================== -->
+  <!-- Compile                                                            -->
+  <!-- ================================================================== -->
+
+  <!--
+     | Compile everything.
+     |
+     | This target should depend on other compile-* targets for each
+     | different type of compile that needs to be performed, short of
+     | documentation compiles.
+   -->
+
+  <target name="compile"
+    description="Compile all source files."
+    depends="compile-classes, compile-etc"/>
+
+  <!-- Compile all class files -->
+  <target name="compile-classes" depends="init">
+    <mkdir dir="${build.classes}"/>
+    <javac destdir="${build.classes}"
+      optimize="${javac.optimize}"
+      target="${javac.target}"
+      source="${javac.source}"
+      debug="${javac.debug}"
+      depend="${javac.depend}"
+      verbose="${javac.verbose}"
+      deprecation="${javac.deprecation}"
+      includeAntRuntime="${javac.include.ant.runtime}"
+      includeJavaRuntime="${javac.include.java.runtime}"
+      includes="${javac.includes}"
+      excludes="${javac.excludes}"
+      failonerror="${javac.fail.onerror}">
+
+      <src path="${source.java}"/>
+      <exclude name="org/jboss/tm/recovery/test/**"/>
+      <classpath refid="javac.classpath"/>
+    </javac>
+  </target>
+
+  <!-- Compile etc files (manifests and such) -->
+  <target name="compile-etc" depends="init">
+    <mkdir dir="${build.etc}"/>
+    <copy todir="${build.etc}" filtering="yes">
+      <fileset dir="${source.etc}">
+        <include name="**/*"/>
+      </fileset>
+    </copy>
+  </target>
+
+
+  <!-- ================================================================== -->
+  <!-- Archives                                                           -->
+  <!-- ================================================================== -->
+
+  <!--
+     | Build all jar files.
+   -->
+  <target name="jars"
+    description="Builds all jar files."
+    depends="_buildmagic:build-bypass-check"
+    unless="build-bypass.on">
+
+    <call target="compile"/>
+
+    <mkdir dir="${build.lib}"/>
+
+    <!-- Build ${module.name}.jar -->
+    <jar jarfile="${build.lib}/${module.name}.jar" manifest="${build.etc}/default.mf">
+      <fileset dir="${build.classes}">
+        <include name="org/jboss/tm/**"/>
+      </fileset>
+    </jar>
+
+    <!-- Build ${module.name}-client.jar -->
+    <jar jarfile="${build.lib}/${module.name}-client.jar" manifest="${build.etc}/default.mf">
+      <fileset dir="${build.classes}">
+        <include name="**/*Exception.class"/>
+        <include name="**/*Error.class"/>
+        <include name="**/*MBean.class"/>
+
+        <!--
+           | jason: not really sure what is meant for the client...
+           |        someone should trim this list.
+         -->
+        <include name="org/jboss/tm/**"/>
+      </fileset>
+    </jar>
+
+    <!--
+       | JBoss/Testsuite Support
+     -->
+
+    <!-- testsuite-support.jar -->
+    <jar jarfile="${build.lib}/testsuite-support.jar" manifest="${build.etc}/default.mf">
+      <fileset dir="${build.classes}">
+        <include name="org/jboss/tm/**"/>
+      </fileset>
+    </jar>
+
+    <!-- Update the build marker to allow bypassing -->
+    <touch file="${build-bypass.marker}"/>
+
+  </target>
+
+  <!-- ================================================================== -->
+  <!-- Install & Release                                                  -->
+  <!-- ================================================================== -->
+
+  <target name="install"
+    description="Install the structure for a release."
+    depends="all, _buildmagic:install:default"/>
+
+  <target name="release" depends="install"/>
+
+  <target name="release-zip"
+    description="Builds a ZIP distribution."
+    depends="release, _buildmagic:release:zip"/>
+
+  <target name="release-tar"
+    description="Builds a TAR distribution."
+    depends="release, _buildmagic:release:tar"/>
+
+  <target name="release-tgz"
+    description="Builds a TAR-GZ distribution."
+    depends="release, _buildmagic:release:tgz"/>
+
+  <target name="release-all"
+    description="Builds a distribution for each archive type."
+    depends="release-zip, release-tgz"/>
+
+
+  <!-- ================================================================== -->
+  <!-- Cleaning                                                           -->
+  <!-- ================================================================== -->
+
+  <!-- Clean up all build output -->
+  <target name="clean"
+    description="Cleans up most generated files."
+    depends="_buildmagic:clean">
+  </target>
+
+  <!-- Clean up all generated files -->
+  <target name="clobber"
+    description="Cleans up all generated files."
+    depends="_buildmagic:clobber, clean">
+  </target>
+
+
+  <!-- ================================================================== -->
+  <!-- Misc.                                                              -->
+  <!-- ================================================================== -->
+
+  <target name="main"
+    description="Executes the default target (most)."
+    depends="most"/>
+
+  <target name="all"
+    description="Builds everything."
+    depends="jars, docs"/>
+
+  <target name="most"
+    description="Builds almost everything."
+    depends="jars"/>
+
+  <target name="help"
+    description="Show this help message."
+    depends="_buildmagic:help:standard"/>
+
+</project>

Added: trunk/transaction/component-info.xml
===================================================================
--- trunk/transaction/component-info.xml	                        (rev 0)
+++ trunk/transaction/component-info.xml	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,26 @@
+<project name="transaction-component-info">
+   <!-- ============================================================ -->
+   <!-- Transaction                                                  -->
+   <!-- ============================================================ -->
+
+   <component id="transaction"
+              module="jboss-transaction"
+              version="5.0-SNAPSHOT"
+              specTitle="JBoss"
+              specVersion="5.0.0"
+              specVendor="JBoss (http://www.jboss.org)"
+              implTitle="JBoss"
+              implURL="http://www.jboss.org"
+              implVersion="5.0.0"
+              implVendor="JBoss.org"               
+   >
+      <artifact id="jboss-transaction.jar"/>
+      <artifact id="jboss-transaction-client.jar"/>
+      <artifact id="transaction-testsuite-support.jar"/>
+      <export>
+         <include input="jboss-transaction.jar"/>
+      </export>
+   </component>
+
+
+</project>

Added: trunk/transaction/jbossbuild.xml
===================================================================
--- trunk/transaction/jbossbuild.xml	                        (rev 0)
+++ trunk/transaction/jbossbuild.xml	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+
+<!--
+ JBoss, the OpenSource J2EE webOS
+ 
+ Distributable under LGPL license.
+ See terms of license at gnu.org.
+-->
+
+<!-- ================================================================== -->
+<!-- Transaction component definition                                        -->
+<!-- ================================================================== -->
+
+<project name="project" 
+         default="build" 
+         basedir="."
+>
+   <import file="../tools/etc/jbossbuild/tasks.xml"/>
+   <import file="component-info.xml"/>
+
+   <!-- =============================================================== -->
+   <!-- The component definition                                        -->
+   <!-- =============================================================== -->
+
+   <componentdef component="transaction" description="JBoss Transaction">
+
+      <!-- ============================================================ -->
+      <!-- The main source                                              -->
+      <!-- ============================================================ -->
+
+      <source id="main" 
+              excludes="org/jboss/tm/recovery/test/**">
+
+         <include component="oswego-concurrent"/>
+         <include component="apache-log4j"/>
+
+         <include component="common"/>
+         <include component="j2se"/>
+         <include component="j2ee"/>
+         <include component="jmx"/>
+         <include component="system"/>
+         <include component="jboss/remoting"/>
+      </source>
+
+      <!-- ============================================================ -->
+      <!-- jboss-transaction.jar                                        -->
+      <!-- ============================================================ -->
+
+      <artifactdef artifact="jboss-transaction.jar">
+         <include input="main">
+            <include pattern="org/jboss/tm/**"/>
+         </include>
+      </artifactdef>
+
+      <!-- ============================================================ -->
+      <!-- jboss-transaction-client.jar                                 -->
+      <!-- ============================================================ -->
+
+      <artifactdef artifact="jboss-transaction-client.jar">
+         <include input="main">
+            <include pattern="org/jboss/tm/**"/>
+         </include>
+      </artifactdef>
+
+      <!-- ============================================================ -->
+      <!-- transaction-testsuite-support.jar                            -->
+      <!-- ============================================================ -->
+
+      <artifactdef artifact="transaction-testsuite-support.jar">
+         <include input="main">
+            <include pattern="org/jboss/tm/**"/>
+         </include>
+      </artifactdef>
+
+   </componentdef>
+
+   <!-- Generate the targets -->   
+   <generate generate="transaction"/>
+
+</project>

Added: trunk/transaction/pom.xml
===================================================================
--- trunk/transaction/pom.xml	                        (rev 0)
+++ trunk/transaction/pom.xml	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,73 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+	<parent>
+		<groupId>org.jboss.jbossas</groupId>
+		<artifactId>jboss-as-parent</artifactId>
+		<version>5.0.0-SNAPSHOT</version>
+		<relativePath>../build/pom.xml</relativePath>
+	</parent>
+	<modelVersion>4.0.0</modelVersion>
+	<groupId>org.jboss.jbossas</groupId>
+	<artifactId>jboss-as-transaction</artifactId>
+	<packaging>jar</packaging>
+	<name>JBoss Application Server Transaction</name>
+	<url>http://www.jboss.com/products/jbossas</url>
+	<description>JBoss Application Server (transaction module)</description>
+	<build>
+		<sourceDirectory>src/main</sourceDirectory>
+		<resources>
+			<resource>
+				<directory>src/resources</directory>
+			</resource>
+		</resources>
+		<plugins>
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-compiler-plugin</artifactId>
+				<configuration>
+					<excludes>
+						<exclude>org/jboss/tm/recovery/test/*.java</exclude>
+					</excludes>
+				</configuration>
+			</plugin>
+			
+		</plugins>
+	</build>
+	<dependencies>
+		<!-- Compile (global dependencies) -->
+		<dependency>
+			<groupId>org.jboss.jbossas</groupId>
+			<artifactId>jboss-as-j2se</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.jboss.jbossas</groupId>
+			<artifactId>jboss-as-system-jmx</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.jboss.jbossas</groupId>
+			<artifactId>jboss-as-main</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>javax.transaction</groupId>
+			<artifactId>jta</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.jboss</groupId>
+			<artifactId>jboss-common-core</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>jboss</groupId>
+			<artifactId>jboss-common-logging-spi</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>org.jboss</groupId>
+			<artifactId>jboss-transaction-spi</artifactId>
+		</dependency>
+		<dependency>
+			<groupId>jboss</groupId>
+			<artifactId>remoting</artifactId>
+		</dependency>
+	</dependencies>
+	
+</project>

Added: trunk/transaction/src/etc/default.mf
===================================================================
--- trunk/transaction/src/etc/default.mf	                        (rev 0)
+++ trunk/transaction/src/etc/default.mf	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,10 @@
+Manifest-Version: 1.0
+Created-By: @java.vm.version@ (@java.vm.vendor@)
+Specification-Title: @specification.title@
+Specification-Version: @specification.version@
+Specification-Vendor: @specification.vendor@
+Implementation-Title: @implementation.title@
+Implementation-URL: @implementation.url@
+Implementation-Version: @implementation.version@
+Implementation-Vendor: @implementation.vendor@
+Implementation-Vendor-Id: @implementation.vendor.id@

Added: trunk/transaction/src/main/org/jboss/tm/CoordinatorFactory.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/CoordinatorFactory.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/CoordinatorFactory.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,41 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+import org.jboss.tm.remoting.interfaces.Coordinator;
+
+/**
+ * Interface of a factory that creates references to DTM or OTS coordinators.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public interface CoordinatorFactory
+{
+   /**
+    * Creates a reference to a DTM or OTS coordinator associated with the
+    * transaction with a given <code>localId</code>.
+    * @param localId the local id of a transaction
+    * @return a <code>Coordinator</code> reference. 
+    */
+   Coordinator createCoordinator(long localId);
+}

Added: trunk/transaction/src/main/org/jboss/tm/GlobalId.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/GlobalId.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/GlobalId.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,226 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+import java.io.IOException;
+import javax.transaction.xa.Xid;
+
+/**
+ *  This object encapsulates the global transaction ID of a transaction.
+ *  It is similar to an Xid, but holds only the GlobalId part.
+ *  This implementation is immutable and always serializable at runtime.
+ *
+ *  @see XidImpl
+ *  @author <a href="mailto:osh at sparre.dk">Ole Husgaard</a>
+ *  @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ *  @version $Revision: 37459 $
+ */
+public class GlobalId
+   implements java.io.Externalizable
+{
+   static final long serialVersionUID = -230282197045463046L;
+
+   private static String thisClassName;
+   
+   static {
+      thisClassName = GlobalId.class.getName();
+      thisClassName = 
+         thisClassName.substring(thisClassName.lastIndexOf('.') + 1);
+   }
+   
+   /**
+    *  Format id of this instance.
+    */
+	private int formatId;
+
+   /**
+    *  Global transaction id of this instance.
+    *  The coding of this class depends on the fact that this variable is
+    *  initialized in the constructor and never modified. References to
+    *  this array are never given away, instead a clone is delivered.
+    */
+   private byte[] globalId;
+
+   /**
+    *  Hash code of this instance. For a native GlobalId (one whose formatId
+    *  is XidImpl.JBOSS_FORMAT_ID), this is really a sequence number.
+    */
+   private int hash;
+
+   // Constructors --------------------------------------------------
+
+   public GlobalId()
+   {
+      // Used for Externalizable support
+   }
+   
+   /**
+    *  Create a new instance. This constructor is package-private, as it
+    *  trusts the hash parameter to be good.
+    */
+   GlobalId(int formatId, byte[] globalId, int hash)
+   {
+      this.formatId = formatId;
+      this.globalId = globalId;
+      this.hash = hash;
+   }
+
+   /**
+    *  Create a new instance. This constructor is public <em>only</em>
+    *  to get around a class loader problem; it should be package-private.
+    */
+   public GlobalId(int formatId, byte[] globalId)
+   {
+      this.formatId = formatId;
+      this.globalId = globalId;
+      hash = computeHash();
+   }
+
+   public GlobalId(Xid xid)
+   {
+      formatId = xid.getFormatId();
+      globalId = xid.getGlobalTransactionId();
+      if (xid instanceof XidImpl && formatId == XidImpl.JBOSS_FORMAT_ID) 
+      {
+         // native GlobalId: use its hash code (a sequence number)
+         hash = xid.hashCode();
+      }
+      else 
+      {
+         // foreign GlobalId: do the hash computation
+         hash = computeHash();
+      }
+   }
+
+   public GlobalId(int formatId, int bqual_length, byte[] tid)
+   {
+      this.formatId = formatId;
+      if (bqual_length == 0)
+         globalId = tid;
+      else 
+      {
+         int len = tid.length - bqual_length;
+         globalId = new byte[len];
+         System.arraycopy(tid, 0, globalId, 0, len);
+      }
+      hash = computeHash();
+   }
+
+   // Public --------------------------------------------------------
+
+   /**
+    *  Returns the global transaction id of this transaction.
+    */
+   public byte[] getGlobalTransactionId()
+   {
+      return (byte[])globalId.clone();
+   }
+
+   /**
+    *  Returns the format identifier of this transaction.
+    */
+   public int getFormatId() 
+   {
+      return formatId;
+   }
+
+   /**
+    *  Compare for equality.
+    *
+    *  Instances are considered equal if they both refer to the same
+    *  global transaction id.
+    */
+   public boolean equals(Object obj)
+   {
+      if (obj instanceof GlobalId) {
+         GlobalId other = (GlobalId)obj;
+
+         if (formatId != other.formatId)
+            return false;
+
+         if (globalId == other.globalId)
+            return true;
+
+         if (globalId.length != other.globalId.length)
+            return false;
+
+         int len = globalId.length;
+         for (int i = 0; i < len; ++i)
+            if (globalId[i] != other.globalId[i])
+               return false;
+
+         return true;
+      }
+      return false;
+   }
+
+   public int hashCode()
+   {
+      return hash;
+   }
+   
+   public String toString() 
+   {
+      return thisClassName + "[formatId=" + formatId
+            + ", globalId=" + new String(globalId).trim()
+            + ", hash=" + hash + "]";
+   }
+   
+   // Externalizable implementation ---------------------------------
+
+   public void writeExternal(java.io.ObjectOutput out)
+      throws IOException
+   {
+      out.writeInt(formatId);
+      out.writeObject(globalId);
+   }
+   
+   public void readExternal(java.io.ObjectInput in)
+      throws IOException, ClassNotFoundException
+   {
+      formatId = in.readInt();
+      globalId = (byte[])in.readObject();
+      hash = computeHash();
+   }
+
+   // Private -------------------------------------------------------
+
+   private int computeHash()
+   {
+      if (formatId == XidImpl.JBOSS_FORMAT_ID)
+      {
+         return (int)TransactionImpl.xidFactory.extractLocalIdFrom(globalId);
+      }
+      else
+      {
+         int len = globalId.length;
+         int hashval = 0;
+         
+         // TODO: use a better hash function
+         for (int i = 0; i < len; ++i)
+            hashval = 3 * globalId[i] + hashval;
+         hashval += formatId;
+         return hashval;
+      }
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/JBossRollbackException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/JBossRollbackException.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/JBossRollbackException.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,129 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import javax.transaction.RollbackException;
+import org.jboss.util.NestedThrowable;
+
+/**
+ * JBossRollbackException.java
+ *
+ *
+ * Created: Sun Feb  9 22:45:03 2003
+ *
+ * @author <a href="mailto:d_jencks at users.sourceforge.net">David Jencks</a>
+ * @version
+ */
+
+public class JBossRollbackException
+   extends RollbackException
+   implements NestedThrowable
+{
+   static final long serialVersionUID = 2924502280803535350L;
+
+   Throwable t;
+
+   public JBossRollbackException()
+   {
+      super();
+   }
+
+   public JBossRollbackException(final String message)
+   {
+      super(message);
+   }
+
+   public JBossRollbackException(final Throwable t)
+   {
+      super();
+      this.t = t;
+   }
+
+   public JBossRollbackException(final String message, final Throwable t)
+   {
+      super(message);
+      this.t = t;
+   }
+
+   // Implementation of org.jboss.util.NestedThrowable
+
+   public Throwable getNested()
+   {
+      return t;
+   }
+
+   public Throwable getCause()
+   {
+      return t;
+   }
+
+   /**
+    * Returns the composite throwable message.
+    *
+    * @return  The composite throwable message.
+    */
+   public String getMessage() {
+      return NestedThrowable.Util.getMessage(super.getMessage(), t);
+   }
+
+   /**
+    * Prints the composite message and the embedded stack trace to the
+    * specified print stream.
+    *
+    * @param stream  Stream to print to.
+    */
+   public void printStackTrace(final PrintStream stream)
+   {
+      if (t == null || NestedThrowable.PARENT_TRACE_ENABLED)
+      {
+         super.printStackTrace(stream);
+      }
+      NestedThrowable.Util.print(t, stream);
+   }
+
+   /**
+    * Prints the composite message and the embedded stack trace to the
+    * specified print writer.
+    *
+    * @param writer  Writer to print to.
+    */
+   public void printStackTrace(final PrintWriter writer)
+   {
+      if (t == null || NestedThrowable.PARENT_TRACE_ENABLED)
+      {
+         super.printStackTrace(writer);
+      }
+      NestedThrowable.Util.print(t, writer);
+   }
+
+   /**
+    * Prints the composite message and the embedded stack trace to
+    * <tt>System.err</tt>.
+    */
+   public void printStackTrace()
+   {
+      printStackTrace(System.err);
+   }
+
+}// JBossRollbackException

Added: trunk/transaction/src/main/org/jboss/tm/JBossXAException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/JBossXAException.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/JBossXAException.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,177 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+import java.io.PrintWriter;
+import java.io.PrintStream;
+import javax.transaction.xa.XAException;
+
+import org.jboss.util.NestedThrowable;
+
+/**
+ * Thrown to indicate a problem with a xaresource related operation.
+ *
+ * <p>
+ * Properly displays linked exception (ie. nested exception)
+ * when printing the stack trace.
+ *
+ * @version <tt>$Revision: 37459 $</tt>
+ * @author  <a href="mailto:adrian at jboss.com">Adrian Brock</a>
+ */
+public class JBossXAException
+   extends XAException
+   implements NestedThrowable
+{
+   /** The servial version uid*/
+   private static final long serialVersionUID = 6614203184612359692L;
+
+   /** The linked exception */
+   Throwable linked;
+   
+   /**
+    * Rethrow as an xa exception if it is not already
+    * 
+    * @param message the message
+    * @param t the original exception
+    * @throws XAException the xa exception
+    */
+   public static void rethrowAsXAException(String message, Throwable t) throws XAException
+   {
+      if (t instanceof XAException)
+         throw (XAException) t;
+      else
+         throw new JBossXAException(message, t);
+   }
+   
+   /**
+    * Construct a <tt>JBossXAException</tt> with the specified detail
+    * message.
+    *
+    * @param msg  Detail message.
+    */
+   public JBossXAException(final String msg)
+   {
+      super(msg);
+   }
+
+   /**
+    * Construct a <tt>JBossXAException</tt> with the specified detail
+    * message and error code.
+    *
+    * @param code  Error code.
+    */
+   public JBossXAException(final int code)
+   {
+      super(code);
+   }
+
+   /**
+    * Construct a <tt>JBossXAException</tt> with the specified detail
+    * message and linked <tt>Exception</tt>.
+    *
+    * @param msg     Detail message.
+    * @param linked  Linked <tt>Exception</tt>.
+    */
+   public JBossXAException(final String msg, final Throwable linked)
+   {
+      super(msg);
+      this.linked = linked;
+   }
+
+   /**
+    * Construct a <tt>JBossXAException</tt> with the specified
+    * linked <tt>Exception</tt>.
+    *
+    * @param linked  Linked <tt>Exception</tt>.
+    */
+   public JBossXAException(final Throwable linked)
+   {
+      this(linked.getMessage(), linked);
+   }
+
+   /**
+    * Return the nested <tt>Throwable</tt>.
+    *
+    * @return  Nested <tt>Throwable</tt>.
+    */
+   public Throwable getNested()
+   {
+      return linked;
+   }
+
+   /**
+    * Return the nested <tt>Throwable</tt>.
+    *
+    * <p>For JDK 1.4 compatibility.
+    *
+    * @return  Nested <tt>Throwable</tt>.
+    */
+   public Throwable getCause()
+   {
+      return linked;
+   }
+
+   /**
+    * Returns the composite throwable message.
+    *
+    * @return  The composite throwable message.
+    */
+   public String getMessage()
+   {
+      return NestedThrowable.Util.getMessage(super.getMessage(), linked);
+   }
+
+   /**
+    * Prints the composite message and the embedded stack trace to the
+    * specified print stream.
+    *
+    * @param stream  Stream to print to.
+    */
+   public void printStackTrace(final PrintStream stream)
+   {
+      if (linked == null || NestedThrowable.PARENT_TRACE_ENABLED)
+         super.printStackTrace(stream);
+      NestedThrowable.Util.print(linked, stream);
+   }
+
+   /**
+    * Prints the composite message and the embedded stack trace to the
+    * specified print writer.
+    *
+    * @param writer  Writer to print to.
+    */
+   public void printStackTrace(final PrintWriter writer)
+   {
+      if (linked == null || NestedThrowable.PARENT_TRACE_ENABLED)
+         super.printStackTrace(writer);
+      NestedThrowable.Util.print(linked, writer);
+   }
+
+   /**
+    * Prints the composite message and the embedded stack trace to
+    * <tt>System.err</tt>.
+    */
+   public void printStackTrace()
+   {
+      printStackTrace(System.err);
+   }
+}

Added: trunk/transaction/src/main/org/jboss/tm/LocalId.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/LocalId.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/LocalId.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,211 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+/**
+ *  LocalId is a wrapper for a long value that identifies a transaction within 
+ *  a JBoss server. This implementation is immutable and serializable at 
+ *  runtime.
+ *
+ *  @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ *  @version $Revision: 37459 $
+ */
+public class LocalId
+   implements java.io.Externalizable
+{
+   static final long serialVersionUID = 2076780468014328911L;
+   
+   /**
+    *  Long value that identifies a transaction within a JBoss server.
+    *  This is really a sequence number generated by the XidFactory.
+    */
+   private long value;
+
+
+   // Static --------------------------------------------------------
+
+   /** Number of bits in the "transaction generation" part of the local id */
+   private static final int TX_GEN_SIZE = 20;
+   
+   /** Number of bits in a long */
+   private static final int LONG_SIZE = 64;
+   
+   /** Number of bits in the "transaction number" part of the local id */
+   private static final int TX_NUM_SIZE = LONG_SIZE - TX_GEN_SIZE;
+   
+   /** Mask to extract a transaction generation from the lower part of a long */  
+   private static final long TX_GEN_MASK = (1L << TX_GEN_SIZE) - 1;
+   
+   /** Mask to extract the transaction number from a local id */  
+   private static final long TX_NUM_MASK = (1L << TX_NUM_SIZE) - 1;
+   
+   /**
+    * Assemble a local id as a long value with a generation number in its high 
+    * part (the <code>TX_GEN_SIZE</code> most significant bits) and a 
+    * transaction number in its low part (the <code>TX_NUM_SIZE</code> least 
+    * significant bits).
+    *   
+    * @param genNumber the generation number
+    * @param txNumber  the transaction number.
+    * @return a local transaction id.
+    */
+   public static long assemble(int genNumber, long txNumber)
+   {
+      return (((long) genNumber) << TX_NUM_SIZE) | (txNumber & TX_NUM_MASK);
+   }
+   
+   /**
+    * Extracts the generation number from a local id value.
+    * 
+    * @param localIdValue a local id value.
+    * @return the generation number in the <code>TX_GEN_SIZE</code> most 
+    *         significant bits of the local id.
+    */
+   public static int toGenerationNumber(long localIdValue)
+   {
+      return (int)((localIdValue >> TX_NUM_SIZE) & TX_GEN_MASK); 
+   }
+
+   /**
+    * Extracts a transaction number from a local id value.
+    * 
+    * @param localIdValue a local id value.
+    * @return the transaction number in the <code>TX_NUM_SIZE</code> least 
+    *         significant bits of the local id.
+    */   
+   public static long toTransactionNumber(long localIdValue)
+   {
+      return localIdValue & TX_NUM_MASK;
+   }
+   
+   /**
+    * Converts a local id value into a string.
+    * 
+    * @param l  a local id value
+    * @return a string of the form "<em>gn</em>:<em>tn</em>", where 
+    *         <em>gn</em> is the generation number and <em>tn</em> is 
+    *         the transaction number.     
+    */
+   public static String toString(long l) 
+   {
+      return "" + toGenerationNumber(l) + ":" + toTransactionNumber(l); 
+   }
+
+   /**
+    * Copies a local id value into a byte array.
+    * 
+    * @param localIdValue a local id value
+    * @param dst the destination byte array
+    * @param dstBegin the index of the first element of <code>dst</code> that
+    *        will receive a byte of the local id.
+    */
+   public static void toByteArray(long localIdValue, byte[] dst, int dstBegin)
+   {
+      dst[dstBegin + 0] = (byte)(0xff & (localIdValue >>> 56));
+      dst[dstBegin + 1] = (byte)(0xff & (localIdValue >>> 48));
+      dst[dstBegin + 2] = (byte)(0xff & (localIdValue >>> 40));
+      dst[dstBegin + 3] = (byte)(0xff & (localIdValue >>> 32));
+      dst[dstBegin + 4] = (byte)(0xff & (localIdValue >>> 24));
+      dst[dstBegin + 5] = (byte)(0xff & (localIdValue >>> 16));
+      dst[dstBegin + 6] = (byte)(0xff & (localIdValue >>>  8));
+      dst[dstBegin + 7] = (byte)(0xff & (localIdValue >>>  0));
+   }
+
+   /**
+    * Gets a local id value from a byte array/
+    * 
+    * @param src the source byte array
+    * @param srcBegin the index of the first element of <code>src</code> that
+    *        contains a byte of the local id.
+    * @return a local id extracted from the byte array.
+    */
+   public static long fromByteArray(byte[] src, int srcBegin) 
+   {
+      return ((long)(src[srcBegin + 0] & 0xff) << 56)
+	 | ((long)(src[srcBegin + 1] & 0xff) << 48)
+	 | ((long)(src[srcBegin + 2] & 0xff) << 40)
+	 | ((long)(src[srcBegin + 3] & 0xff) << 32)
+	 | ((long)(src[srcBegin + 4] & 0xff) << 24)
+	 | ((long)(src[srcBegin + 5] & 0xff) << 16)
+	 | ((long)(src[srcBegin + 6] & 0xff) << 8)
+	 | ((long)(src[srcBegin + 7] & 0xff));
+   }
+
+   // Constructors --------------------------------------------------
+
+   /**
+    * No-arg constructor for Externalizable support.
+    */
+   public LocalId()
+   {
+   }
+   
+   /**
+    *  Create a new instance. This constructor is public <em>only</em>
+    *  to get around a class loader problem; it should be package-private.
+    */
+   public LocalId(long value)
+   {
+      this.value = value;
+   }
+
+   public LocalId(XidImpl xid)
+   {
+      this(xid.getLocalIdValue());
+   }
+
+   // Public --------------------------------------------------------
+
+   public long getValue()
+   {
+      return value;
+   }
+
+   /**
+    *  Compare for equality.
+    */
+   public boolean equals(Object obj)
+   {
+      return (obj instanceof LocalId) ? (value == ((LocalId)obj).value) 
+                                      : false;
+   }
+
+   public int hashCode()
+   {
+      return (int)value;
+   }
+   
+   // Externalizable implementation ---------------------------------
+   public void writeExternal(java.io.ObjectOutput out)
+      throws java.io.IOException
+   {
+      out.writeLong(value);
+   }
+   
+   public void readExternal(java.io.ObjectInput in)
+      throws java.io.IOException, ClassNotFoundException
+   {
+      value = in.readLong();
+   }
+
+}
+

Added: trunk/transaction/src/main/org/jboss/tm/OTSContextFactory.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/OTSContextFactory.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/OTSContextFactory.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,84 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+import org.jboss.tm.remoting.interfaces.Coordinator;
+
+/**
+ * Interface of a factory that creates OTS propagation contexts.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public interface OTSContextFactory
+{
+   /**
+    * Creates an OTS context with the specified <code>formatId</code>, 
+    * <code>globalId</code>, and <code>coordinator</code>.  The OTS context
+    * will contain a null <code>Terminator</code> reference and an
+    * <code>org.omg.CosTransactions.Coordinator</code> reference extracted from 
+    * the <code>coordinator</code> parameter, which must be an instance of
+    * <code>org.jboss.tm.iiop.wrapper.OTSCoordinatorWrapper</code>.
+    * It will contain zero in its timeout field. The caller should use the 
+    * method <code>setTimeout</code> to set the timeout field of the newly
+    * created OTS context.  
+    * 
+    * @param formatId the format id to be stored in the OTS context
+    * @param globalId the global id to be stored in the OTS context
+    * @param coordinator an <code>org.jboss.tm.iiop.wrapper.OTSCoordinatorWrapper</code>
+    *        containing the <code>org.omg.CosTransactions.Coordinator</code>
+    *        reference to be stored in the OTS context
+    * @return an instance of <code>org.omg.CosTransactions.PropagationContext</code>. 
+    */
+   Object createOTSContext(int formatId, 
+                           byte[] globalId, 
+                           Coordinator coordinator);
+
+   /**
+    * Creates an OTS context with the specified <code>formatId</code> and 
+    * <code>globalId</code>, and with an 
+    * <code>org.omg.CosTransactions.Coordinator</code> reference that 
+    * corresponds to the local transaction whose local id is 
+    * <code>coordinatorLocalId</code>. The OTS context will contain a null 
+    * <code>Terminator</code> reference and zero in its timeout field.
+    * The caller should use the method <code>setTimeout</code> to set the 
+    * timeout field of the newly created OTS context.  
+    * 
+    * @param formatId the format id to be stored in the OTS context
+    * @param globalId the global id to be stored in the OTS context
+    * @param coordinatorLocalId the local id of the transaction associated with
+    *        the coordinator reference to be stored in the OTS context
+    * @return an instance of <code>org.omg.CosTransactions.PropagationContext</code>. 
+    */
+   Object createOTSContext(int formatId, 
+                           byte[] globalId, 
+                           long coordinatorLocalId);
+
+   /**
+    * Sets the timeout field of the specified OTS context.
+    * @param otsContext an instance of <code>org.omg.CosTransactions.PropagationContext</code>
+    * @param timeout the timeout value, in seconds, to be stored in the OTS 
+    *                context.
+    */
+   void setTimeout(Object otsContext, int timeout);
+   
+}

Added: trunk/transaction/src/main/org/jboss/tm/ResourceFactory.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/ResourceFactory.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/ResourceFactory.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,46 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+import org.jboss.tm.remoting.interfaces.Resource;
+
+/**
+ * Interface of a factory that creates references to DTM or OTS resources.
+ * In order to register itself as a subcoordinator with the parent coordinator,
+ * a <code>TransactionImpl</code> instance needs to obtain a reference to a
+ * resource associated with itself. It calls a <code>ResourceFactory</code> to
+ * obtain that reference, which it then uses as the resource parameter in a 
+ * call to <code>registerResource</code> on the parent coordinator.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public interface ResourceFactory
+{
+   /**
+    * Creates a reference to a DTM or OTS resource that corresponds to the
+    * transaction with a given <code>localId</code>.
+    * @param localId the local id of a transaction
+    * @return a <code>Resource</code> reference
+    */
+   Resource createResource(long localId);
+}

Added: trunk/transaction/src/main/org/jboss/tm/StringRemoteRefConverter.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/StringRemoteRefConverter.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/StringRemoteRefConverter.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,74 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+import org.jboss.tm.remoting.interfaces.RecoveryCoordinator;
+import org.jboss.tm.remoting.interfaces.Resource;
+
+/**
+ * Converts stringfied references to remote <code>Resource</code>s and 
+ * <code>RecoveryCoordinator</code>s back to remote references. This 
+ * interface serves the purpose of avoiding a dependency from the transaction
+ * recovery module to the CORBA/OTS module.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public interface StringRemoteRefConverter
+{
+   /**
+    * Converts a stringfied reference to a remote <code>Resource</code>
+    * back to a remote reference.
+    *  
+    * @param strResource a stringfied reference to a remote 
+    *                    <code>Resource</code> 
+    * @return a remote reference to the <code>Resource</code>.
+    */
+   Resource stringToResource(String strResource);
+   
+   /**
+    * Converts a stringfied reference to a remote 
+    * <code>RecoveryCoordinator</code> back to a remote reference.
+    *  
+    * @param strRecCoordinator a stringfied reference to a remote 
+    *                          <code>RecoveryCoordinator</code>
+    * @return a remote reference to the <code>RecoveryCoordinator</code>
+    */
+   RecoveryCoordinator stringToRecoveryCoordinator(String strRecCoordinator);
+
+   /**
+    * Takes a remote reference to a resource and converts it to a string.
+    * 
+    * @param res a remote reference to a resource
+    * @return a string that represents the remote resource.
+    */
+   String resourceToString(Resource res);
+   
+   /**
+    * Takes a remote reference to recovery coordinator and converts it to a 
+    * string. 
+    * 
+    * @param recoveryCoord a remote reference to a recovery coordinator
+    * @return a string that represents the remote recovery coordinator.
+    */
+   String recoveryCoordinatorToString(RecoveryCoordinator recoveryCoord);
+}

Added: trunk/transaction/src/main/org/jboss/tm/TMUtil.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/TMUtil.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/TMUtil.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,94 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+
+import org.jboss.logging.Logger;
+
+/**
+ * Transaction Manager utility methods.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public class TMUtil
+{
+   
+   private static final Logger log = 
+      Logger.getLogger(TMUtil.class);
+
+   private static TransactionManager tm;
+   
+   // enforce non-instantiability
+   private TMUtil()
+   {
+   }
+   
+   /**
+    *  Gets a <code>Transaction</code> instance given its <code>LocalId</code>.
+    */
+   public static Transaction getTransaction(LocalId localId)
+   {
+      TransactionPropagationContextImporter tpcImporter =
+         TransactionPropagationContextUtil.getTPCImporter();
+      
+      return tpcImporter.importTransactionPropagationContext(localId);
+   }
+
+   /**
+    *  Gets a reference to the transaction manager.
+    */
+   public static TransactionManager getTransactionManager()
+   {
+      if (tm == null)
+      {
+         try
+         {
+            Context ctx = new InitialContext();
+            tm = (TransactionManager)ctx.lookup("java:/TransactionManager");
+         }
+         catch (NamingException ex)
+         {
+            log.error("java:/TransactionManager lookup failed", ex);
+         }
+      }
+      return tm;
+   }
+   
+   /**
+    * Time-out conversion from milliseconds to seconds. 
+    */
+   public static int divideAndRoundUp(long m, long n)
+   {
+      long retval = m / n;
+
+      if ((m % n) != 0)
+         retval = retval + 1;
+      return (int)retval ;
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/TransactionImpl.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/TransactionImpl.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/TransactionImpl.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,5402 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+import java.lang.reflect.Proxy;
+import java.rmi.NoSuchObjectException;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.resource.spi.work.Work;
+import javax.resource.spi.work.WorkCompletedException;
+import javax.resource.spi.work.WorkException;
+import javax.transaction.HeuristicCommitException;
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.Synchronization;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionRolledbackException;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import org.jboss.logging.Logger;
+import org.jboss.tm.integrity.TransactionIntegrity;
+import org.jboss.tm.recovery.HeuristicStatus;
+import org.jboss.tm.recovery.LogRecord;
+import org.jboss.tm.recovery.RecoveryLogger;
+import org.jboss.tm.recovery.RecoveryTestingException;
+import org.jboss.tm.recovery.TxCompletionHandler;
+import org.jboss.tm.recovery.XAResourceAccess;
+import org.jboss.tm.recovery.XAWork;
+import org.jboss.tm.remoting.interfaces.Coordinator;
+import org.jboss.tm.remoting.interfaces.HeuristicHazardException;
+import org.jboss.tm.remoting.interfaces.RecoveryCoordinator;
+import org.jboss.tm.remoting.interfaces.Resource;
+import org.jboss.tm.remoting.interfaces.TransactionAlreadyPreparedException;
+import org.jboss.tm.remoting.interfaces.TransactionInactiveException;
+import org.jboss.tm.remoting.interfaces.TransactionNotPreparedException;
+import org.jboss.tm.remoting.interfaces.TxPropagationContext;
+import org.jboss.tm.remoting.interfaces.Vote;
+import org.jboss.util.timeout.Timeout;
+import org.jboss.util.timeout.TimeoutFactory;
+import org.jboss.util.timeout.TimeoutTarget;
+
+/**
+ * Our <code>Transaction</code> implementation.
+ *
+ * @author <a href="mailto:rickard.oberg at telkel.com">Rickard Oberg</a>
+ * @author <a href="mailto:marc.fleury at telkel.com">Marc Fleury</a>
+ * @author <a href="mailto:osh at sparre.dk">Ole Husgaard</a>
+ * @author <a href="mailto:toby.allsopp at peace.com">Toby Allsopp</a>
+ * @author <a href="mailto:jason at planet57.com">Jason Dillon</a>
+ * @author <a href="mailto:d_jencks at users.sourceforge.net">David Jencks</a>
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:adrian at jboss.com">Adrian Brock</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @author <a href="mailto:dimitris at jboss.org">Dimitris Andreadis</a>
+ * @version $Revision: 64098 $
+ * @see TxManager
+ */
+public class TransactionImpl
+        implements Transaction, TimeoutTarget
+{
+   // Constants -----------------------------------------------------
+
+   /**
+    * Code meaning "no heuristics seen", 
+    * must not be XAException.XA_HEURxxx
+    */
+   private static final int HEUR_NONE = 0;
+
+   /**
+    * Code meaning "heuristics seen, but no additional info is available", 
+    * must not be XAException.XA_HEURxxx
+    */
+   private static final int HEUR_UNKNOWN = XAException.XA_RETRY;
+
+   // Resource states
+   private final static int RS_NEW = 0; // not yet enlisted
+   private final static int RS_ENLISTED = 1; // enlisted
+   private final static int RS_SUSPENDED = 2; // suspended
+   private final static int RS_ENDED = 3; // not associated
+   private final static int RS_VOTE_READONLY = 4; // voted read-only
+   private final static int RS_VOTE_OK = 5; // voted ok
+   private final static int RS_FORGOT = 6; // RM has forgotten
+   private final static int RS_COMMITTED = 7; // committed
+   private final static int RS_ROLLEDBACK = 8; // rolledback
+   private final static int RS_HEUR_OUTCOME = 8; // had an heuristic outcome
+   private final static int RS_ERROR = 9; // error condition (no use retrying)
+
+   // Attributes ----------------------------------------------------
+
+   /**
+    * True if trace messages should be logged.
+    */
+   private boolean trace = log.isTraceEnabled();
+
+   /**
+    * The ID of this transaction.
+    */
+   private XidImpl xid;
+
+   private HashSet threads = new HashSet(1);
+
+   private Map transactionLocalMap = Collections.synchronizedMap(new HashMap());
+
+   private Throwable cause;
+
+   /**
+    * True for a foreign transaction that has been imported either through an
+    * DTM/OTS transaction propagation context or via JCA transaction inflow;
+    * false for a locally started transaction.
+    */
+   private final boolean foreignTx;
+
+   /**
+    * This transaction's parent coordinator, or null if this transaction does
+    * not have a parent coordinator. A transaction with no parent coodinator
+    * is either a (locally started) root transaction or a foreign transaction
+    * that has been imported via JCA transaction inflow.
+    */
+   private Coordinator parentCoordinator = null;
+
+   /**
+    * The resource registered by transaction with the parent coordinator.
+    */
+   private Resource registeredResource = null;
+   
+   /**
+    * This transaction's recovery coordinator, or null this transaction did not
+    * register itself as a remote resource with the parent coordinator.
+    */
+   private RecoveryCoordinator recoveryCoordinator = null;
+   
+   /**
+    * The inbound branch qualifier, if this is a foreign transaction that has
+    * been imported via JCA transaction inflow, or null otherwise.   
+    */
+   private byte[] inboundBranchQualifier = null;
+
+   /**
+    * The synchronizations to call back.
+    */
+   private Synchronization[] sync = new Synchronization[3];
+
+   /**
+    * Size of allocated synchronization array.
+    */
+   private int syncAllocSize = 3;
+
+   /**
+    * Count of synchronizations for this transaction.
+    */
+   private int syncCount = 0;
+
+   /**
+    * A list of the XAResources that have participated in this transaction.
+    */
+   private ArrayList xaResources = new ArrayList(3);
+
+   /**
+    * A list of the remote resources that have participated in this transaction.
+    */
+   private ArrayList remoteResources = new ArrayList(3);
+
+   /**
+    * The XAResource used in the last resource gambit
+    */
+   private EnlistedXAResource lastResource;
+
+   /**
+    * Flags that it is too late to enlist new resources.
+    */
+   private boolean resourcesEnded = false;
+
+   /**
+    * Last branch id used.
+    */
+   private long lastBranchId = 0;
+
+   /**
+    * Status of this transaction.
+    */
+   private int status;
+
+   /**
+    * The heuristics status of this transaction.
+    */
+   private int heuristicCode = HEUR_NONE;
+
+   /**
+    * The time when this transaction was started.
+    */
+   private long start;
+
+   /**
+    * The timeout handle for this transaction.
+    */
+   private Timeout timeout;
+
+   /**
+    * Timeout in millisecs
+    */
+   private long timeoutPeriod;
+
+   /**
+    * Mutex for thread-safety. This should only be changed in the
+    * <code>lock()</code> and <code>unlock()</code> methods.
+    */
+   private Thread locked = null;
+   
+   /**
+    * The lock depth
+    */
+   private int lockDepth = 0;
+
+   /**
+    * Any current work associated with the transaction
+    */
+   private Work work;
+
+   /**
+    * Flags that we are done with this transaction and that it can be reused.
+    */
+   private boolean done = false;
+
+   /**
+    * This transaction's DTM propagation context.
+    */
+   private TxPropagationContext dtmPropagationContext = null;
+
+   /**
+    * This transaction's OTS propagation context.
+    */
+   private Object otsPropagationContext = null;
+
+   /**
+    * This transaction's TxCompletionHandler.
+    */
+   private TxCompletionHandler completionHandler = null;
+   
+   /**
+    * Number of XA resources that reported transient problems.
+    */
+   private int xaResourcesToRetry = 0;
+   
+   /**
+    * Number of remote resources that reported transient problems. 
+    */
+   private int remoteResourcesToRetry = 0;
+   
+   /**
+    * Timeout before calling <code>replayCompletion</code> on the recovery
+    * the coordinator when waiting for the coordinator in the prepared state. 
+    */
+   private Timeout preparedTimeout = null;
+   
+   /**
+    * Timeout before retrying commit or rollback calls on XA resources that
+    * reported transient problems. 
+    */
+   private Timeout xaRetryTimeout = null;
+   
+   /**
+    * List of <code>EnlistedXAResource</code> instances with heuristic 
+    * decisions.
+    */
+   private List xaResourcesWithHeuristicDecisions = null;
+   
+   /**
+    * List of <code>EnlistedRemoteResource</code> instances with heuristic 
+    * decisions.
+    */
+   private List remoteResourcesWithHeuristicDecisions = null;
+   
+   /**
+    * Counts how many resources were committed.
+    */
+   private int committedResources = 0;
+   
+   /**
+    * Counts how many resources were rolled back.
+    */
+   private int rolledbackResources = 0;
+   
+   /**
+    * True if this <code>TransactionImpl</code> could not reach a resource 
+    * during the second phase of 2PC. 
+    */
+   private boolean heuristicHazard = false;
+      
+   // Static --------------------------------------------------------
+
+   /**
+    * Class logger, we don't want a new logger with every transaction.
+    */
+   private static Logger log = Logger.getLogger(TransactionImpl.class);
+
+   /**
+    * Factory for Xid instances of specified class.
+    * This is set from the <code>TransactionManagerService</code>
+    * MBean.
+    */
+   static XidFactoryBase xidFactory;
+
+   static XAExceptionFormatter xaExceptionFormatter;
+
+   /** The timeout factory */
+   static TimeoutFactory timeoutFactory = TimeoutFactory.getSingleton();
+   
+   /**
+    * CoordinatorFactory that creates remote references to DTM coordinators,
+    * or null if the DTM is not employed.
+    */
+   private static CoordinatorFactory dtmCoordinatorFactory = null;
+
+   /**
+    * ResourceFactory that creates remote references to DTM resources,
+    * or null if the DTM is not employed.
+    */
+   private static ResourceFactory dtmResourceFactory = null;
+
+   /**
+    * ResourceFactory that creates remote references to OTS resources,
+    * or null if OTS is not employed.
+    */
+   private static ResourceFactory otsResourceFactory = null;
+
+   /**
+    * Factory that creates OTS transaction propagation contexts,
+    * or null if OTS is not employed.
+    */
+   private static OTSContextFactory otsContextFactory = null;
+
+   /**
+    * True if coordinator interposition is enabled.
+    */
+   private static boolean interpositionEnabled = false;
+
+   /**
+    * Object that converts between strings and remote references for
+    * DTM objects, or null if DTM is not employed.
+    */
+   private static StringRemoteRefConverter dtmStrRemoteRefConverter = null;
+   
+   /**
+    * Object that converts between strings and remote references for
+    * OTS objects, or null if OTS is not employed.
+    */
+   private static StringRemoteRefConverter otsStrRemoteRefConverter = null;
+   
+   /**
+    * This static code is only present for testing purposes so a
+    * tm can be usable without a lot of setup.
+    */
+   public static XidFactoryBase defaultXidFactory()
+   {
+      if (xidFactory == null)
+      {
+         XidFactoryImpl impl = new XidFactoryImpl();
+         impl.start();
+         xidFactory = impl;
+      }
+      return xidFactory;
+   }
+
+   /**
+    * Setter for <code>dtmCoordinatorFactory</code>.
+    */
+   static void setDTMCoordinatorFactory(CoordinatorFactory dtmCoordinatorFactory)
+   {
+      TransactionImpl.dtmCoordinatorFactory = dtmCoordinatorFactory;
+   }
+
+   /**
+    * Setter for <code>dtmResourceFactory</code>.
+    */
+   static void setDTMResourceFactory(ResourceFactory dtmResourceFactory)
+   {
+      TransactionImpl.dtmResourceFactory = dtmResourceFactory;
+   }
+
+   /**
+    * Setter for <code>otsResourceFactory</code>.
+    */
+   static void setOTSResourceFactory(ResourceFactory otsResourceFactory)
+   {
+      TransactionImpl.otsResourceFactory = otsResourceFactory;
+   }
+
+   /**
+    * Setter for <code>otsContextFactory</code>.
+    */
+   static void setOTSContextFactory(OTSContextFactory otsContextFactory)
+   {
+      TransactionImpl.otsContextFactory = otsContextFactory;
+   }
+
+   /**
+    * Setter for <code>interpositionEnabled</code>.
+    */
+   static void setInterpositionEnabled(boolean interpositionEnabled)
+   {
+      TransactionImpl.interpositionEnabled = interpositionEnabled;
+   }
+
+   /**
+    * Getter for <code>interpositionEnabled</code>.
+    */
+   static boolean getInterpositionEnabled()
+   {
+      return TransactionImpl.interpositionEnabled;
+   }
+
+   /**
+    * Setter for <code>dtmStrRemoteRefConverter</code>.
+    */
+   static void setDTMStrRemoteRefConverter(
+         StringRemoteRefConverter dtmStrRemoteRefConverter)
+   {
+      TransactionImpl.dtmStrRemoteRefConverter = dtmStrRemoteRefConverter;
+   }
+
+   /**
+    * Setter for <code>otsStrRemoteRefConverter</code>.
+    */
+   static void setOTSStrRemoteRefConverter(
+         StringRemoteRefConverter otsStrRemoteRefConverter)
+   {
+      TransactionImpl.otsStrRemoteRefConverter = otsStrRemoteRefConverter;
+   }
+
+   /**
+    * Converts a stringfied reference to a remote resource back to a remote 
+    * reference.
+    *  
+    * @param strResource a stringfied reference to a remote resource
+    * @return a remote reference to the resource.
+    */
+   static Resource stringToResource(String strResource)
+   {
+      if (strResource.startsWith("IOR:"))
+      {
+         if (otsStrRemoteRefConverter != null)
+            return otsStrRemoteRefConverter.stringToResource(strResource);
+         else
+            throw new IllegalArgumentException();
+      }
+      else
+      {
+         if (dtmStrRemoteRefConverter != null)
+            return dtmStrRemoteRefConverter.stringToResource(strResource);
+         else
+            throw new IllegalArgumentException();
+      }
+   }
+
+   /**
+    * Converts a stringfied reference to a remote recovery coordinator back 
+    * to a remote reference.
+    *  
+    * @param strRecCoordinator a stringfied reference to a remote recovery
+    *                          coordinator
+    * @return a remote reference to the recovery coordinator.
+    */
+   static RecoveryCoordinator stringToRecoveryCoordinator(
+                                                   String strRecCoordinator)
+   {
+      if (strRecCoordinator.startsWith("IOR:"))
+      {
+         if (otsStrRemoteRefConverter != null)
+            return otsStrRemoteRefConverter.stringToRecoveryCoordinator(
+                                                            strRecCoordinator);
+         else
+            throw new IllegalArgumentException();
+      }
+      else
+      {
+         if (dtmStrRemoteRefConverter != null)
+            return dtmStrRemoteRefConverter.stringToRecoveryCoordinator(
+                                                            strRecCoordinator);
+         else
+            throw new IllegalArgumentException();
+      }
+   }
+
+   /**
+    * Takes a remote reference to a resource and converts it to a string.
+    * 
+    * @param res a remote reference to a resource
+    * @return a string that represents the remote resource.
+    */
+   static String resourceToString(Resource res)
+   {
+      if (Proxy.isProxyClass(res.getClass()))
+      {
+         if (dtmStrRemoteRefConverter != null)
+            return dtmStrRemoteRefConverter.resourceToString(res);
+         else
+            throw new IllegalArgumentException();
+      }
+      else
+      {
+         if (otsStrRemoteRefConverter != null)
+            return otsStrRemoteRefConverter.resourceToString(res);
+         else
+            throw new IllegalArgumentException();
+      }         
+   }
+
+   /**
+    * Takes a remote reference to a recovery coordinator and converts it to a 
+    * string. 
+    * 
+    * @param recoveryCoord a remote reference to a recovery coordinator
+    * @return a string that represents the remote recovery coordinator.
+    */
+   static String recoveryCoordinatorToString(RecoveryCoordinator recoveryCoord)
+   {
+      if (Proxy.isProxyClass(recoveryCoord.getClass()))
+      {
+         if (dtmStrRemoteRefConverter != null)
+            return dtmStrRemoteRefConverter.recoveryCoordinatorToString(
+                                                               recoveryCoord);
+         else
+            throw new IllegalArgumentException();
+      }
+      else
+      {
+         if (otsStrRemoteRefConverter != null)
+            return otsStrRemoteRefConverter.recoveryCoordinatorToString(
+                                                               recoveryCoord);
+         else
+            throw new IllegalArgumentException();
+      }         
+   }
+   
+   // Constructors --------------------------------------------------
+
+   /**
+    * Constructor for transactions started locally.
+    */
+   TransactionImpl(long timeout)
+   {
+      foreignTx = false;
+      xid = xidFactory.newXid();
+
+      status = Status.STATUS_ACTIVE;
+
+      start = System.currentTimeMillis();
+      this.timeout = timeoutFactory.createTimeout(start + timeout, this);
+      this.timeoutPeriod = timeout;
+      if (trace)
+         log.trace("Created new instance for tx=" + toString());
+   }
+
+   /**
+    * Constructor for foreign transactions imported through DTM/OTS transaction
+    * propagation contexts.
+    */
+   TransactionImpl(GlobalId gid, Coordinator parentCoordinator, long timeout)
+   {
+      foreignTx = true;
+      xid = xidFactory.newBranch(gid);
+
+      this.parentCoordinator = parentCoordinator;
+
+      status = Status.STATUS_ACTIVE;
+
+      start = System.currentTimeMillis();
+      this.timeout = timeoutFactory.createTimeout(start + timeout, this);
+      this.timeoutPeriod = timeout;
+      if (trace)
+         log.trace("Created new instance for tx=" + toString());
+   }
+
+   /**
+    * Constructor for foreign transactions imported through the JCA transaction
+    * inflow mechanism.
+    */
+   TransactionImpl(GlobalId gid, byte[] inboundBranchQualifier, long timeout)
+   {
+      foreignTx = true;
+      xid = xidFactory.newBranch(gid);
+
+      this.inboundBranchQualifier = inboundBranchQualifier;
+
+      status = Status.STATUS_ACTIVE;
+
+      start = System.currentTimeMillis();
+      this.timeout = timeoutFactory.createTimeout(start + timeout, this);
+      this.timeoutPeriod = timeout;
+      if (trace)
+         log.trace("Created new instance for tx=" + toString());
+      
+   }
+   
+   /**
+    * Constructor to recreate a locally-started transaction that does not 
+    * involve other transaction managers. It is intended to be called at 
+    * recovery time, for recreating transactions that were in the committing 
+    * state when the server crashed. Such a transaction completed the first 
+    * phase of the 2PC protocol and logged the commit decision, but it must
+    * still send commit messages to some or all of its <code>XAResource</code>s.
+    * 
+    * @param localId the local id of a locally-started transaction that is in 
+    *             the committing state and does not involve other transaction 
+    *             managers
+    * @param preparedXAWorkList a list of <code>org.jboss.tm.XAWork</code>
+    *             instances containing one element for each 
+    *             <code>XAResource</code> that is enlisted with the transaction 
+    *             and is still in the prepared state
+    * @param completionHandler the 
+    *             <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be
+    *             notifed when the second phase of the 2PC completes
+    * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+    *             contaning information on a locally-detected heuristic hazard.           
+    */
+   TransactionImpl(long localId, 
+                   List preparedXAWorkList, 
+                   TxCompletionHandler completionHandler,
+                   LogRecord.HeurData heurData)
+   {
+      foreignTx = false;
+      xid = xidFactory.recreateXid(localId);
+      status = Status.STATUS_COMMITTING;
+      if (heurData != null)
+      {
+         heuristicCode = heurData.heuristicStatusCode;
+         heuristicHazard = heurData.locallyDetectedHeuristicHazard;
+      }
+      this.completionHandler = completionHandler;
+      for (Iterator it = preparedXAWorkList.iterator(); it.hasNext(); )
+      {
+         XAWork preparedXAWork = (XAWork) it.next();
+         EnlistedXAResource r = new EnlistedXAResource(preparedXAWork);
+         xaResources.add(r);
+
+      }
+      commitXAResourcesAfterTimeout();
+   }
+         
+   /**
+    * Constructor to recreate a locally-started transaction that involves other
+    * transaction managers. Involving other transaction managers means that 
+    * there are remote <code>Resource</code>s enlisted with the transaction. 
+    * This constructor is intended to be called at recovery time, for recreating 
+    * transactions that were in the committing state when the server crashed. 
+    * Such a transaction completed the first phase of the 2PC protocol and 
+    * logged the commit decision, but it must still send commit messages to 
+    * some or all of its resources (<code>XAResource</code>s and remote 
+    * <code>Resource</code>s).
+    * 
+    * @param localId the local id of a locally-started transaction that is in 
+    *             the committing state and involves other transaction managers
+    * @param preparedXAWorkList list of <code>org.jboss.tm.XAWork</code>
+    *             instances containing one element for each 
+    *             <code>XAResource</code> that is enlisted with the transaction
+    *             and is still in the prepared state
+    * @param resources an array with stringfied references for the remote 
+    *             <code>Resource</code>s enlisted with the transaction 
+    * @param completionHandler the 
+    *             <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be 
+    *             notifed when the second phase of the 2PC completes
+    * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+    *             contaning information on a locally-detected heuristic hazard.           
+    */
+   TransactionImpl(long localId, 
+                   List preparedXAWorkList, 
+                   String[] resources,
+                   TxCompletionHandler completionHandler,
+                   LogRecord.HeurData heurData)
+   {
+      foreignTx = false;
+      xid = xidFactory.recreateXid(localId);
+      status = Status.STATUS_COMMITTING;
+      this.completionHandler = completionHandler;
+      if (heurData != null)
+      {
+         heuristicCode = heurData.heuristicStatusCode;
+         heuristicHazard = heurData.locallyDetectedHeuristicHazard;
+      }
+      for (Iterator it = preparedXAWorkList.iterator(); it.hasNext(); )
+      {
+         XAWork preparedXAWork = (XAWork) it.next();
+         EnlistedXAResource r = new EnlistedXAResource(preparedXAWork);
+         xaResources.add(r);
+      }
+      for (int i = 0; i < resources.length; i++)
+      {
+         EnlistedRemoteResource r =
+            new EnlistedRemoteResource(stringToResource(resources[i]), true);
+         remoteResources.add(r);
+      }
+      lock();
+      try
+      {
+         retryCommitRemoteResources();
+      }
+      finally
+      {
+         unlock();
+      }
+      if (preparedXAWorkList.size() > 0)
+      {
+         // Keep this TransactionImpl around to
+         // commit its XAResources at a later time.
+         commitXAResourcesAfterTimeout();
+      }
+      else if (remoteResourcesToRetry == 0 && heuristicCode == HEUR_NONE)
+      {
+         // This TransactionImpl is not needed anymore. 
+         lock();
+         try
+         {
+            completeTransaction();
+         }
+         finally
+         {
+            unlock();
+         }
+      }
+      else
+      {
+         // Do nothing. Just keep this TransactionImpl around to receive 
+         // replayCompletion calls from remote resources.
+      }
+   }
+
+   /**
+    * Constructor to recreate a foreign transaction that entered this virtual 
+    * machine in a transaction context propagated along with a remote method 
+    * invocation. This constructor is intended to be called at recovery time, 
+    * for recreating transactions that were in the prepared state when the 
+    * server crashed. 
+    * 
+    * @param localId the local id of a foreign transaction that entered this 
+    *             virtual machine in a transaction propagation context and is
+    *             propagated along with a remote method invocation and is in 
+    *             the prepared state
+    * @param inboundFormatId format id part of the foreign transaction 
+    *             identifier that entered this virtual machine
+    * @param globalTransactionId global id part of the foreign transaction 
+    *             identifier that entered this virtual machine
+    * @param recoveryCoord an stringfied reference to the transaction branch's
+    *             <code>RecovertyCoordinator</code>
+    * @param preparedXAWorkList a list of <code>org.jboss.tm.XAWork</code>
+    *             instances containing one element for each 
+    *             <code>XAResource</code> enlisted with the transaction 
+    * @param resources an array with stringfied references for the remote 
+    *             <code>Resource</code>s enlisted with the transaction 
+    * @param completionHandler the 
+    *             <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be 
+    *             notifed when the second phase of the 2PC completes
+    * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+    *             contaning information on a locally-detected heuristic hazard.           
+    */
+   TransactionImpl(long localId, 
+                   int inboundFormatId,
+                   byte[] globalTransactionId,
+                   String recoveryCoord,
+                   List preparedXAWorkList,
+                   String[] resources,
+                   TxCompletionHandler completionHandler,
+                   LogRecord.HeurData heurData)
+   {
+      foreignTx = true;
+      GlobalId globalId = new GlobalId(inboundFormatId, globalTransactionId);
+      xid = xidFactory.recreateXid(localId, globalId);
+      recoveryCoordinator = stringToRecoveryCoordinator(recoveryCoord);
+      if (heurData == null)
+         status = Status.STATUS_PREPARED;
+      else
+      {
+         status = heurData.transactionStatus;
+         heuristicCode = heurData.heuristicStatusCode;
+         heuristicHazard = heurData.locallyDetectedHeuristicHazard;
+      }
+      this.completionHandler = completionHandler;
+      for (Iterator it = preparedXAWorkList.iterator(); it.hasNext(); )
+      {
+         XAWork preparedXAWork = (XAWork) it.next();
+         EnlistedXAResource r = new EnlistedXAResource(preparedXAWork);
+         xaResources.add(r);
+      }
+      for (int i = 0; i < resources.length; i++)
+      {
+         EnlistedRemoteResource r =
+            new EnlistedRemoteResource(stringToResource(resources[i]), true);
+         remoteResources.add(r);
+      }
+      
+      // Set my registeredResource using the appropriate resource factory.
+      if (Proxy.isProxyClass(recoveryCoordinator.getClass()))
+      {
+         // DTM coordinator case
+         if (dtmResourceFactory != null)
+            registeredResource = dtmResourceFactory.createResource(localId);
+         else
+            log.warn("Error reconstructing TransactionImpl instance for tx=" 
+                     + toString() + " -- DTM resource factory missing." );
+      }
+      else
+      {
+         // OTS coordinator case
+         if (otsResourceFactory != null)
+            registeredResource = otsResourceFactory.createResource(localId);
+         else
+            log.warn("Error reconstructing TransactionImpl instance for tx=" + 
+                     toString() + " -- OTS resource factory missing." );
+      }
+
+      if (status == Status.STATUS_PREPARED)
+      {
+         if (registeredResource != null)
+         {
+            createPreparedTimeout();
+            if (trace)
+               log.trace("Calling replayCompletion " +
+                         "on the recovery coordinator, tx=" + toString());
+            try
+            {
+               recoveryCoordinator.replayCompletion(registeredResource);
+            }
+            catch (NoSuchObjectException noCoordinator)
+            {
+               if (trace)
+               {
+                  log.trace("Exception in replayCompletion: no coordinator for tx=" 
+                            + toString(), noCoordinator);
+                  log.trace("Rolling back transaction branch, tx=" + toString());
+               }
+               try
+               {
+                  rollbackBranch();
+               }
+               catch (Exception e)
+               {
+                  if (trace)
+                     log.trace("Exception in transaction branch rollback, tx=" + 
+                               toString(), e);
+               }
+            }
+            catch (Exception ignore)
+            {
+               if (trace)
+                  log.trace("Ignoring exception in replayCompletion, tx=" + 
+                            toString(), ignore);
+            }
+         }
+         else
+            log.warn("Error reconstructing TransactionImpl instance for tx=" + 
+                     toString() + " -- registeredResource not set.\n" +
+                     "The remote coordinator will NOT be contacted.");
+      }
+      else if (status == Status.STATUS_COMMITTING)
+      {
+         lock();
+         try
+         {
+            retryCommitRemoteResources();
+            
+            if (preparedXAWorkList.size() > 0)
+            {
+               // Keep this TransactionImpl around to
+               // commit its XAResources at a later time.
+               commitXAResourcesAfterTimeout();
+            }
+            else if (remoteResourcesToRetry == 0 && heuristicCode == HEUR_NONE)
+            {
+               // This TransactionImpl is not needed anymore. 
+               completeTransaction();
+            }
+            else
+            {
+               // Do nothing. Just keep this TransactionImpl around to 
+               // receive replayCompletion calls from remote resources.
+            }
+         }
+         finally
+         {
+            unlock();
+         }
+      }
+      else if (status == Status.STATUS_ROLLING_BACK)
+      {
+         try
+         {
+            rollbackResourcesAndCompleteTransaction();
+         }
+         finally
+         {
+            unlock();
+         }
+      }
+   }
+   
+   /**
+    * Constructor to recreate a foreign transaction that entered this virtual 
+    * machine through JCA transaction inflow. This constructor is intended to 
+    * be called at recovery time, for recreating transactions that were in the 
+    * prepared state when the server crashed.  
+    * 
+    * @param localId the local id of a foreign transaction that entered this 
+    *             virtual machine in a transaction propagation context and is
+    *             propagated along with a remote method invocation and is in 
+    *             the prepared state
+    * @param inboundFormatId format id part of the foreign <code>Xid</code> 
+    * @param globalTransactionId global id part of the foreign <code>Xid</code> 
+    * @param inboundBranchQualifier the branch qualifier part of the foreign 
+    *             <code>Xid</code> 
+    * @param preparedXAWorkList a list of <code>org.jboss.tm.XAWork</code>
+    *             instances containing one element for each 
+    *             <code>XAResource</code> enlisted with the transaction 
+    * @param resources an array with stringfied references for the remote 
+    *             <code>Resource</code>s enlisted with the transaction 
+    * @param completionHandler the 
+    *             <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be 
+    *             notifed when the second phase of the 2PC completes
+    * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+    *             contaning information on a locally-detected heuristic hazard.           
+    */
+   TransactionImpl(long localId, 
+                   int inboundFormatId,
+                   byte[] globalTransactionId,
+                   byte[] inboundBranchQualifier,
+                   List preparedXAWorkList,
+                   String[] resources,
+                   TxCompletionHandler completionHandler,
+                   LogRecord.HeurData heurData)
+   {
+      foreignTx = true;
+      GlobalId globalId = new GlobalId(inboundFormatId, globalTransactionId);
+      xid = xidFactory.recreateXid(localId, globalId);
+      if (heurData == null)
+         status = Status.STATUS_PREPARED;
+      else
+      {
+         status = heurData.transactionStatus;
+         heuristicCode = heurData.heuristicStatusCode;
+         heuristicHazard = heurData.locallyDetectedHeuristicHazard;
+      }
+      this.inboundBranchQualifier = inboundBranchQualifier;
+      this.completionHandler = completionHandler;
+
+      for (Iterator it = preparedXAWorkList.iterator(); it.hasNext(); )
+      {
+         XAWork preparedXAWork = (XAWork) it.next();
+         EnlistedXAResource r = new EnlistedXAResource(preparedXAWork);
+         xaResources.add(r);
+      }
+      
+      for (int i = 0; i < resources.length; i++)
+      {
+         EnlistedRemoteResource resource =
+            new EnlistedRemoteResource(stringToResource(resources[i]), true);
+         remoteResources.add(resource);
+      }
+      
+      if (status == Status.STATUS_COMMITTING)
+      {
+         lock();
+         try
+         {
+            retryCommitRemoteResources();
+            
+            if (xaResourcesToRetry > 0)
+            {
+               // Keep this TransactionImpl around to
+               // commit its XAResources at a later time.
+               commitXAResourcesAfterTimeout();
+            }
+            else if (remoteResourcesToRetry == 0 && heuristicCode == HEUR_NONE)
+            {
+               // This TransactionImpl is not needed anymore. 
+               completeTransaction();
+            }
+            else
+            {
+               // Do nothing. Just keep this TransactionImpl around to 
+               // receive replayCompletion calls from remote resources.
+            }
+         }
+         finally
+         {
+            unlock();
+         }
+      }
+      else if (status == Status.STATUS_ROLLING_BACK)
+      {
+         try
+         {
+            rollbackResourcesAndCompleteTransaction();
+         }
+         finally
+         {
+            unlock();
+         }
+      }
+   }
+   
+   /**
+    * Constructor to recreate a transaction that is in a heuristically 
+    * completed state (either committed or rolledback). This constructor is 
+    * intended to be called at recovery time, for recreating heuristically 
+    * completed transactions that were not yet forgotten when the server
+    * crashed.  
+    * 
+    * @param heurData an instance of <code>LogRecord.HeurData</code> with
+    *             information on the heuristically completed transaction 
+    * @param xaResourcesWithHeuristics a list of 
+    *             <code>org.jboss.tm.XAWork</code>
+    *             instances containing one element for each 
+    *             <code>XAResource</code> that is in a a heuristic status
+    * @param completionHandler the 
+    *             <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be 
+    *             notifed when the second phase of the 2PC completes.
+    */
+   TransactionImpl(LogRecord.HeurData heurData, 
+                   List xaResourcesWithHeuristics,
+                   TxCompletionHandler completionHandler)
+   {
+      foreignTx = heurData.foreignTx;
+      status = heurData.transactionStatus;
+      
+      if (status != Status.STATUS_COMMITTING &&
+            status != Status.STATUS_COMMITTED && 
+            status != Status.STATUS_ROLLING_BACK &&
+            status != Status.STATUS_ROLLEDBACK)
+         throw new RuntimeException("Attempt to recreate heuristically " +
+                                    "completed transaction with status " +
+                                    TxUtils.getStatusAsString(status) + ". " +
+                                    "Must be STATUS_COMMITTING, " +
+                                    "STATUS_COMMITTED, STATUS_ROLLING_BACK, " +
+                                    "or STATUS_ROLLEDBACK.");
+      if (!foreignTx)
+         xid = xidFactory.recreateXid(heurData.localTransactionId);
+      else
+      {
+         GlobalId globalId = new GlobalId(heurData.formatId, 
+                                          heurData.globalTransactionId);
+         xid = xidFactory.recreateXid(heurData.localTransactionId, globalId);
+      }
+      inboundBranchQualifier = heurData.inboundBranchQualifier;
+      this.completionHandler = completionHandler;
+      heuristicCode = heurData.heuristicStatusCode;
+      if (!xaResourcesWithHeuristics.isEmpty())
+      {
+         xaResourcesWithHeuristicDecisions = new ArrayList();
+         for (Iterator it = xaResourcesWithHeuristics.iterator(); 
+              it.hasNext(); )
+         {
+            XAWork xaWork = (XAWork) it.next();
+            xaWork.xaResourceAccess.release();
+            EnlistedXAResource r = new EnlistedXAResource(
+                     xaWork.res, 
+                     xaWork.xid,
+                     (heurData.transactionStatus == Status.STATUS_COMMITTED));
+            xaResources.add(r);
+            xaResourcesWithHeuristicDecisions.add(r);
+         }
+      }
+      if (heurData.remoteResourceHeuristics != null && 
+            heurData.remoteResourceHeuristics.length > 0)
+      {
+         remoteResourcesWithHeuristicDecisions = new ArrayList();
+         for (int i = 0; i < heurData.remoteResourceHeuristics.length; i++)
+         {
+            HeuristicStatus heurStatus = heurData.remoteResourceHeuristics[i];
+            EnlistedRemoteResource r = 
+               new EnlistedRemoteResource(
+                                       stringToResource(heurStatus.resourceRef),
+                                       true, /* committed */
+                                       heurStatus.code);
+            remoteResources.add(r);
+            remoteResourcesWithHeuristicDecisions.add(r);
+         }
+      }
+   }
+
+   /**
+    * Returns true if this is a foreign transaction and false if this is a
+    * locally started transaction.
+    *
+    * @return true for a transaction that has been imported either through a
+    *         DTM/OTS transaction propagation context or through JCA
+    *         transaction inflow, and false for a locally started transaction.
+    */
+   public boolean isImported()
+   {
+      return foreignTx;
+   }
+
+   // Implements TimeoutTarget --------------------------------------
+
+   /**
+    * Called when our timeout expires.
+    */
+   public void timedOut(Timeout timeout)
+   {
+      lock();
+      try
+      {
+         log.warn("Transaction " + toString() + " timed out." +
+                  " status=" + TxUtils.getStatusAsString(status));
+
+         if (this.timeout == null)
+            return; // Don't race with timeout cancellation.
+         this.timeout = null;
+
+         switch (status)
+         {
+         case Status.STATUS_ROLLEDBACK:
+         case Status.STATUS_COMMITTED:
+         case Status.STATUS_NO_TRANSACTION:
+            return; // Transaction done.
+
+         case Status.STATUS_ROLLING_BACK:
+            return; // Will be done shortly.
+
+         case Status.STATUS_PREPARED:
+         case Status.STATUS_COMMITTING:
+            return; // Timeout expiration has no effect anymore.
+
+         case Status.STATUS_ACTIVE:
+            status = Status.STATUS_MARKED_ROLLBACK;
+            // fall through..
+         case Status.STATUS_MARKED_ROLLBACK:
+            // don't rollback for now, this messes up with the TxInterceptor.
+            interruptThreads();
+            return;
+
+         case Status.STATUS_PREPARING:
+            status = Status.STATUS_MARKED_ROLLBACK;
+            return; // commit will fail
+
+         default:
+            log.warn("Unknown status at timeout, tx=" + toString());
+            return;
+         }
+      }
+      finally
+      {
+         unlock();
+      }
+   }
+
+   // Implements Transaction ----------------------------------------
+
+   public void commit()
+           throws RollbackException,
+                  HeuristicMixedException,
+                  HeuristicRollbackException,
+                  java.lang.SecurityException,
+                  java.lang.IllegalStateException,
+                  SystemException
+   {
+      lock();
+      try
+      {
+         if (trace)
+            log.trace("Committing, tx=" + toString() +
+                      ", status=" + TxUtils.getStatusAsString(status));
+
+         beforePrepare();
+
+         if (status == Status.STATUS_ACTIVE)
+         {
+            switch (getCommitStrategy())
+            {
+            case 0:
+               // Zero phase commit is really fast ;-)
+               if (trace)
+                  log.trace("Zero phase commit " + toString() + 
+                            ": No resources.");
+               status = Status.STATUS_COMMITTED;
+               break;
+            case 1:
+               // One phase commit
+               if (trace)
+                  log.trace("One phase commit " + toString() + 
+                            ": One resource.");
+               commitResources(true);
+               break;
+            default:
+               // Two phase commit
+               if (trace)
+                  log.trace("Two phase commit " + toString() + 
+                            ": Many resources.");
+
+               if (!prepareResources())
+               {
+                  boolean commitDecision =
+                        status == Status.STATUS_PREPARED &&
+                           (heuristicCode == HEUR_NONE || 
+                                 heuristicCode == XAException.XA_HEURCOM);
+
+                  if (commitDecision)
+                  {
+                     // Save decision to stable storage  
+                     // for recovery after system crash.
+                     try
+                     {
+                        RecoveryLogger logger = 
+                           TxManager.getInstance().getRecoveryLogger();
+                        if (logger != null)
+                        {
+                           completionHandler = logger.saveCommitDecision(
+                                    xid.getLocalIdValue(),
+                                    getStringfiedRemoteResourcesThatVotedCommit());
+                        }
+                     }
+                     catch (Throwable e)
+                     {
+                        if (e instanceof RecoveryTestingException)
+                           throw (RecoveryTestingException) e;
+                        log.warn("FAILED WHEN WRITING COMMIT RECORD."
+                                 + " Rolling back now.", e);
+                        status = Status.STATUS_MARKED_ROLLBACK;
+                        cause = e;
+                     }
+                     cancelTimeout();
+                     if (status == Status.STATUS_PREPARED)
+                     {
+                        try
+                        {
+                           commitResources(false);
+                        }
+                        catch (Throwable e)
+                        {
+                           if (e instanceof RecoveryTestingException)
+                              throw (RecoveryTestingException) e;
+                           log.warn("Unexpected exception in commitResources:", 
+                                    e);
+                        }
+                     }
+                  }
+               }
+               else
+                  status = Status.STATUS_COMMITTED; // all was read-only
+            }
+         }
+
+         if (status == Status.STATUS_COMMITTING)
+         {
+            // Keep this TransactionImpl around.
+            if (xaResourcesToRetry > 0)
+               commitXAResourcesAfterTimeout();
+            checkHeuristicsButDoNotThrowHeuristicHazard();
+         }
+         else if (status != Status.STATUS_COMMITTED)
+         {
+            Throwable causedByThrowable = cause;
+            rollbackResourcesAndCompleteTransaction();
+
+            // throw jboss rollback exception with the saved off cause
+            throw new JBossRollbackException("Unable to commit, tx=" +
+                                             toString() + ", status=" +
+                                             TxUtils.getStatusAsString(status),
+                                             causedByThrowable);
+         }
+         else /* (status == Status.STATUS_COMMITTED) */ 
+         {
+            cancelTimeout();
+            doAfterCompletion();
+            checkHeuristicsButDoNotThrowHeuristicHazard();
+            instanceDone();
+            
+            if (trace)
+               log.trace("Committed OK, tx=" + toString());
+         }
+      }
+      finally
+      {
+         unlock();
+      }
+   }
+
+   public void rollback()
+           throws java.lang.IllegalStateException,
+                  java.lang.SecurityException,
+                  SystemException
+   {
+      lock();
+      try
+      {
+
+         if (trace)
+            log.trace("rollback(): Entered, tx=" + toString() +
+                      ", status=" + TxUtils.getStatusAsString(status));
+
+         checkWork();
+
+         switch (status)
+         {
+         case Status.STATUS_ACTIVE:
+            status = Status.STATUS_MARKED_ROLLBACK;
+            // fall through..
+         case Status.STATUS_MARKED_ROLLBACK:
+            endResources();
+            rollbackResourcesAndCompleteTransaction();
+            // Cannot throw heuristic exception, so we just have to
+            // clear the heuristics without reporting.
+            heuristicCode = HEUR_NONE;
+            return;
+         case Status.STATUS_PREPARING:
+            // Set status to avoid race with prepareResources().
+            status = Status.STATUS_MARKED_ROLLBACK;
+            return; // commit() will do rollback.
+         default:
+            throw new IllegalStateException("Cannot rollback(), tx=" +
+                                            toString() + ", status=" +
+                                            TxUtils.getStatusAsString(status));
+         }
+      }
+      finally
+      {
+         Thread.interrupted();// clear timeout that did an interrupt
+         unlock();
+      }
+   }
+
+   public boolean delistResource(XAResource xaRes, int flag)
+           throws java.lang.IllegalStateException,
+                  SystemException
+   {
+      if (xaRes == null)
+         throw new IllegalArgumentException("null xaRes tx=" + toString());
+      if (flag != XAResource.TMSUCCESS &&
+          flag != XAResource.TMSUSPEND &&
+          flag != XAResource.TMFAIL)
+         throw new IllegalArgumentException("Bad flag: " + flag +
+                                            " tx=" + toString());
+
+      lock();
+      try
+      {
+         if (trace)
+            log.trace("delistResource(): Entered, tx=" + toString() + 
+                      ", status=" + TxUtils.getStatusAsString(status));
+
+         EnlistedXAResource resource = findResource(xaRes);
+         if (resource == null)
+            throw new IllegalArgumentException("xaRes not enlisted " + xaRes);
+
+         switch (status)
+         {
+         case Status.STATUS_ACTIVE:
+         case Status.STATUS_MARKED_ROLLBACK:
+            break;
+         case Status.STATUS_PREPARING:
+            throw new IllegalStateException("Already started preparing, tx=" +
+                                            toString());
+         case Status.STATUS_ROLLING_BACK:
+            throw new IllegalStateException("Already started rolling back, tx=" 
+                                            + toString());
+         case Status.STATUS_PREPARED:
+            throw new IllegalStateException("Already prepared, tx=" + 
+                                            toString());
+         case Status.STATUS_COMMITTING:
+            throw new IllegalStateException("Already started committing, tx=" +
+                                            toString());
+         case Status.STATUS_COMMITTED:
+            throw new IllegalStateException("Already committed, tx=" + 
+                                            toString());
+         case Status.STATUS_ROLLEDBACK:
+            throw new IllegalStateException("Already rolled back, tx=" + 
+                                            toString());
+         case Status.STATUS_NO_TRANSACTION:
+            throw new IllegalStateException("No transaction, tx=" + toString());
+         case Status.STATUS_UNKNOWN:
+            throw new IllegalStateException("Unknown state, tx=" + toString());
+         default:
+            throw new IllegalStateException("Illegal status: " +
+                                            TxUtils.getStatusAsString(status) +
+                                            ", tx=" + toString());
+         }
+
+         try
+         {
+            return resource.delistResource(xaRes, flag);
+         }
+         catch (XAException xae)
+         {
+            logXAException(xae);
+            status = Status.STATUS_MARKED_ROLLBACK;
+            cause = xae;
+            return false;
+         }
+      }
+      finally
+      {
+         unlock();
+      }
+   }
+
+   public boolean enlistResource(XAResource xaRes)
+           throws RollbackException,
+                  java.lang.IllegalStateException,
+                  SystemException
+   {
+      if (xaRes == null)
+         throw new IllegalArgumentException("null xaRes, tx=" + toString());
+
+      lock();
+      try
+      {
+         if (trace)
+            log.trace("enlistResource(): Entered, tx=" + toString() + 
+                      ", status=" + TxUtils.getStatusAsString(status) + 
+                      ", xaRes=" + xaRes);
+
+         checkStatus();
+
+         if (resourcesEnded)
+            throw new IllegalStateException("Too late to enlist resources, tx=" 
+                                            + toString());
+
+         // Add resource
+         try
+         {
+            EnlistedXAResource resource = findResource(xaRes);
+
+            // Existing resource
+            if (resource != null)
+            {
+               if (resource.isEnlisted())
+               {
+                  if (trace)
+                     log.trace("Already enlisted: tx=" + toString() + 
+                               ", status=" + TxUtils.getStatusAsString(status) + 
+                               ", xaRes=" + xaRes);
+                  return true; // already enlisted
+               }
+               if (resource.isDelisted(xaRes))
+               // this is a resource that returns false on all calls to
+               // isSameRM.  Further, the last resource enlisted has
+               // already been delisted, so it is time to enlist it again.
+                  resource = null;
+               else
+                  return resource.startResource();
+            }
+            
+            // Register itself as a resource with the parent coordinator
+            if (parentCoordinator != null && recoveryCoordinator == null)
+               registerResourceWithParentCoordinator();
+
+            resource = findResourceManager(xaRes);
+            if (resource != null)
+            {
+               // The xaRes is new. We register the xaRes with the Xid
+               // that the RM has previously seen from this transaction,
+               // and note that it has the same RM.
+               resource = addResource(xaRes, resource.getXid(), resource);
+               return resource.startResource();
+            }
+
+            // New resource and new RM: Create a new transaction branch.
+            resource = addResource(xaRes, createXidBranch(), null);
+            return resource.startResource();
+         }
+         catch (XAException xae)
+         {
+            logXAException(xae);
+            cause = xae;
+            return false;
+         }
+      }
+      finally
+      {
+         unlock();
+      }
+
+   }
+
+   public int getStatus()
+   {
+      if (done)
+         return Status.STATUS_NO_TRANSACTION;
+      return status;
+   }
+
+   public void registerSynchronization(Synchronization s)
+           throws RollbackException,
+                  java.lang.IllegalStateException,
+                  SystemException
+   {
+      if (s == null)
+         throw new IllegalArgumentException("Null synchronization, tx=" + 
+                                            toString());
+
+      lock();
+      try
+      {
+         if (trace)
+         {
+            log.trace("registerSynchronization(): Entered, " +
+                      "tx=" + toString() +
+                      ", status=" + TxUtils.getStatusAsString(status));
+         }
+
+         checkStatus();
+
+         if (syncCount == syncAllocSize)
+         {
+            // expand table
+            syncAllocSize = 2 * syncAllocSize;
+
+            Synchronization[] sy = new Synchronization[syncAllocSize];
+            System.arraycopy(sync, 0, sy, 0, syncCount);
+            sync = sy;
+         }
+         sync[syncCount++] = s;
+         
+         // Register itself as a resource with the parent coordinator
+         if (parentCoordinator != null && recoveryCoordinator == null)
+            registerResourceWithParentCoordinator();
+      }
+      finally
+      {
+         unlock();
+      }
+   }
+
+   public void setRollbackOnly()
+           throws java.lang.IllegalStateException,
+                  SystemException
+   {
+      lock();
+      try
+      {
+         if (trace)
+            log.trace("setRollbackOnly(): Entered, tx=" + toString() + 
+                      ", status=" + TxUtils.getStatusAsString(status));
+
+         switch (status)
+         {
+         case Status.STATUS_ACTIVE:
+            // Register itself as a resource with the parent coordinator
+            if (parentCoordinator != null && recoveryCoordinator == null)
+            {
+               try
+               {
+                  registerResourceWithParentCoordinator();
+               }
+               catch (RollbackException e)
+               {
+                  log.warn("RollbackException in setRollbackOnly: " + e);
+               }
+            }
+            // fall through..
+         case Status.STATUS_PREPARING:
+         case Status.STATUS_PREPARED:
+            status = Status.STATUS_MARKED_ROLLBACK;
+            // fall through..
+         case Status.STATUS_MARKED_ROLLBACK:
+         case Status.STATUS_ROLLING_BACK:
+            return;
+         case Status.STATUS_COMMITTING:
+            throw new IllegalStateException("Already started committing, tx=" +
+                                            toString());
+         case Status.STATUS_COMMITTED:
+            throw new IllegalStateException("Already committed, tx=" + 
+                                            toString());
+         case Status.STATUS_ROLLEDBACK:
+            throw new IllegalStateException("Already rolled back, tx=" + 
+                                            toString());
+         case Status.STATUS_NO_TRANSACTION:
+            throw new IllegalStateException("No transaction, tx=" + toString());
+         case Status.STATUS_UNKNOWN:
+            throw new IllegalStateException("Unknown state, tx=" + toString());
+         default:
+            throw new IllegalStateException("Illegal status: " +
+                                            TxUtils.getStatusAsString(status) +
+                                            ", tx=" + toString());
+         }
+      }
+      finally
+      {
+         unlock();
+      }
+   }
+
+   // Public methods called by DTM/OTS servants ---------------------
+   
+   /**
+    * Enlists a remote DTM/OTS resource in this transaction. Called by the
+    * DTM/OTS servants to implement the register resource method of the
+    * DTM/OTS coordinator interface.
+    *
+    * @param remoteRes a remote DTM/OTS resource to be enlisted in this
+    *                  transaction
+    * @throws RollbackException
+    * @throws java.lang.IllegalStateException
+    *
+    */
+   public void enlistRemoteResource(Resource remoteRes)
+           throws RollbackException,
+                  java.lang.IllegalStateException
+   {
+      if (remoteRes == null)
+         throw new IllegalArgumentException("null remoteRes, tx=" + toString());
+
+      lock();
+      try
+      {
+         if (trace)
+            log.trace("enlistRemoteResource(): Entered, tx=" + toString() + 
+                      ", status=" + TxUtils.getStatusAsString(status));
+
+         checkStatus();
+
+         if (resourcesEnded)
+            throw new IllegalStateException("Too late to enlist resources, tx=" 
+                                            + toString());
+
+         // Register itself as a resource with the parent coordinator
+         if (parentCoordinator != null && recoveryCoordinator == null)
+            registerResourceWithParentCoordinator();
+
+         // Add resource
+         EnlistedRemoteResource resource =
+                 new EnlistedRemoteResource(remoteRes);
+         remoteResources.add(resource);
+      }
+      finally
+      {
+         unlock();
+      }
+   }
+
+   /**
+    * Prepare an external transaction
+    *
+    * @param inboundXid a foreign Xid that entered this VM via JCA transaction
+    *         inflow, or null in the case of an external transaction that 
+    *         entered this VM in a transaction context propagated along with a 
+    *         remote request. 
+    * @return XAResource.XA_RDONLY or XAResource.XA_OK
+    */
+   public int prepare(Xid inboundXid)
+           throws HeuristicHazardException, 
+                  HeuristicMixedException,
+                  HeuristicRollbackException,
+                  RollbackException
+   {
+      lock();
+      try
+      {
+         if (trace)
+            log.trace("Preparing, tx=" + toString() +
+                      ", status=" + TxUtils.getStatusAsString(status));
+
+         checkWork();
+
+         beforePrepare();
+
+         if (status == Status.STATUS_ACTIVE)
+         {
+            switch (getCommitStrategy())
+            {
+            case 0:
+               // Nothing to do, but we must be careful with synchronizations
+               if (trace)
+                  log.trace("Prepare foreign tx=" + toString() + 
+                            ": No resources.");
+               if (syncCount == 0)
+               {
+                  // No Synchronizations registered: we can vote read-only
+                  status = Status.STATUS_COMMITTED;
+                  completeTransaction();
+                  return XAResource.XA_RDONLY;
+               }
+               else
+               {  // There is at least one Synchronization registered:
+                  // vote commit so that afterCompletion gets called 
+                  status = Status.STATUS_PREPARED;
+                  return XAResource.XA_OK;
+               }
+            default:
+               // Two phase commit
+               if (trace)
+                  log.trace("Prepare foreign tx=" + toString() + 
+                            ": one or more resources.");
+                if (!prepareResources())
+                {
+                   boolean commitDecision =
+                          status == Status.STATUS_PREPARED &&
+                          (heuristicCode == HEUR_NONE ||
+                           heuristicCode == XAException.XA_HEURCOM);
+                   
+                   if (commitDecision)
+                   {
+                      // Save decision to stable storage 
+                      // for recovery after system crash.
+                      try
+                      {
+                         RecoveryLogger logger = 
+                            TxManager.getInstance().getRecoveryLogger();
+                         if (logger != null)
+                         {
+                            if (inboundXid == null)
+                            {
+                               completionHandler = logger.savePrepareDecision(
+                                  xid.getLocalIdValue(),
+                                  xid.getFormatId(),
+                                  xid.getGlobalTransactionId(),
+                                  recoveryCoordinatorToString(recoveryCoordinator),
+                                  getStringfiedRemoteResourcesThatVotedCommit());
+                            }
+                            else
+                            {
+                               completionHandler = logger.savePrepareDecision(
+                                  xid.getLocalIdValue(),
+                                  inboundXid,
+                                  getStringfiedRemoteResourcesThatVotedCommit());
+                            }
+                         }
+                      }
+                      catch (Throwable e)
+                      {
+                         if (e instanceof RecoveryTestingException)
+                            throw (RecoveryTestingException) e;
+                         log.warn("FAILED WHEN WRITING PREPARE RECORD."
+                                  + " Rolling back now.");
+                      }
+                   }
+                }
+                else
+                {
+                   if (syncCount == 0)
+                   {
+                      // No Synchronizations registered: we can vote read-only
+                      if (trace)
+                         log.trace("Prepared foreign tx=" + toString() + 
+                                   ": All readonly," +
+                                   " no Synchronizations registered");
+                      status = Status.STATUS_COMMITTED;
+                      completeTransaction();
+                      return XAResource.XA_RDONLY;
+                   }
+                   else
+                   {  // There is at least one Synchronization registered:
+                      // vote commit so that afterCompletion gets called 
+                      if (trace)
+                         log.trace("Prepared foreign tx=" + toString() + 
+                                   ": All readonly, but there is at least" +
+                                   " one Synchronization registered");
+                      status = Status.STATUS_PREPARED;
+                      return XAResource.XA_OK;
+                   }
+                }
+                break;
+            }
+         }
+
+         if (status != Status.STATUS_PREPARED)
+         {
+            // save off the cause throwable as 
+            // instanceDone resets it to null
+            Throwable causedByThrowable = cause;
+            rollbackResourcesAndCompleteTransaction();
+
+            // throw jboss rollback exception with the saved off cause
+            throw new JBossRollbackException("Unable to prepare, tx=" +
+                                             toString() + ", status=" +
+                                             TxUtils.getStatusAsString(status),
+                                             causedByThrowable);
+         }
+         
+         if (inboundXid == null)
+            createPreparedTimeout();
+         
+         // We are ok to commit
+         return XAResource.XA_OK;
+      }
+      finally
+      {
+         unlock();
+      }
+   }
+
+   /**
+    * Commit an external transaction
+    *
+    * @param onePhase whether the commit is one or two phase
+    */
+   public void commit(boolean onePhase)
+           throws RollbackException,
+                  HeuristicHazardException,
+                  HeuristicMixedException,
+                  HeuristicRollbackException,
+                  SystemException
+   {
+      checkWork();
+
+      // One phase commit optimization
+      if (onePhase)
+      {
+         commit();
+         return;
+      }
+
+      // Two phase
+      lock();
+      try
+      {
+         if (trace)
+            log.trace("Committing two phase, tx=" + toString() +
+                      ", status=" + TxUtils.getStatusAsString(status));
+
+         cancelPreparedTimeout();
+         
+         switch (status)
+         {
+         case Status.STATUS_PREPARING:
+            throw new IllegalStateException("Still preparing, tx=" + 
+                                            toString());
+         case Status.STATUS_ROLLING_BACK:
+            throw new IllegalStateException("Already started rolling back, tx="
+                                            + toString());
+         case Status.STATUS_ROLLEDBACK:
+            instanceDone();
+            //checkHeuristics();
+            throw new IllegalStateException("Already rolled back, tx=" + 
+                                            toString());
+         case Status.STATUS_COMMITTING:
+            if (trace)
+               log.trace("Already committing, tx=" + toString());
+            return;
+         case Status.STATUS_COMMITTED:
+            instanceDone();
+            //checkHeuristics();
+            if (trace)
+               log.trace("Already committed, tx=" + toString());
+            return;
+         case Status.STATUS_NO_TRANSACTION:
+            throw new IllegalStateException("No transaction, tx=" + toString());
+         case Status.STATUS_UNKNOWN:
+            throw new IllegalStateException("Unknown state, tx=" + toString());
+         case Status.STATUS_MARKED_ROLLBACK:
+            endResources();
+            rollbackResourcesAndCompleteTransaction();
+            checkHeuristics();
+            throw new RollbackException("Already marked for rollback, tx=" + 
+                                        toString());
+         case Status.STATUS_PREPARED:
+            break;
+         default:
+            throw new IllegalStateException("Illegal status: " +
+                                            TxUtils.getStatusAsString(status) +
+                                            ", tx=" + toString());
+         }
+
+         commitResources(false);
+         
+         if (status == Status.STATUS_COMMITTING)
+         {
+            // Keep this TransactionImpl around.
+            if (xaResourcesToRetry > 0)
+               commitXAResourcesAfterTimeout();
+            checkHeuristics();
+         }
+         else if (status != Status.STATUS_COMMITTED)
+         {
+            Throwable causedByThrowable = cause;
+
+            // throw jboss rollback exception with the saved off cause
+            throw new JBossRollbackException("Unable to commit, tx=" +
+                                             toString() + ", status=" +
+                                             TxUtils.getStatusAsString(status),
+                                             causedByThrowable);
+         }
+         else /* (status == Status.STATUS_COMMITTED) */ 
+         {
+            cancelTimeout();
+            doAfterCompletion();
+            checkHeuristics();
+            instanceDone();
+
+            if (trace)
+               log.trace("Committed OK, tx=" + toString());
+         }
+      }
+      finally
+      {
+         unlock();
+      }
+   }
+
+   /**                                                                          
+    * Rollback an external transaction                                          
+    */
+   public void rollbackBranch()
+           throws HeuristicCommitException,
+                  HeuristicMixedException,
+                  HeuristicHazardException,
+                  java.lang.IllegalStateException
+   {
+      lock();
+      try
+      {
+
+         if (trace)
+            log.trace("rollbackBranch(): Entered, tx=" + toString() + 
+                      ", status=" + TxUtils.getStatusAsString(status));
+
+         checkWork();
+         cancelPreparedTimeout();
+         
+         switch (status)
+         {
+         case Status.STATUS_ACTIVE:
+         case Status.STATUS_PREPARED:
+            status = Status.STATUS_MARKED_ROLLBACK;
+            // fall through..                                                
+         case Status.STATUS_MARKED_ROLLBACK:
+            endResources();
+            rollbackResourcesAndCompleteTransaction();
+            switch (getFullHeuristicCode())
+            {
+            case XAException.XA_HEURHAZ:
+               if (trace)
+                  log.trace("Throwing HeuristicHazardException, tx=" + 
+                            toString() + ", status=" + 
+                            TxUtils.getStatusAsString(status));
+               throw new HeuristicHazardException();
+            case XAException.XA_HEURMIX:
+               if (trace)
+                  log.trace("Throwing HeuristicMixedException, tx=" + 
+                            toString() + ", status=" + 
+                            TxUtils.getStatusAsString(status));
+               throw new HeuristicMixedException();
+            case XAException.XA_HEURRB:
+               if (trace)
+                  log.trace("Not throwing HeuristicRollbackException, tx=" +
+                            toString() + ", status=" + 
+                            TxUtils.getStatusAsString(status));
+               break;
+            case XAException.XA_HEURCOM:
+               if (trace)
+                  log.trace("Throwing HeuristicCommitException, tx=" +
+                            toString() + ", status=" + 
+                            TxUtils.getStatusAsString(status));
+               throw new HeuristicCommitException();
+            }
+            return;
+         case Status.STATUS_PREPARING:
+            // Set status to avoid race with prepareResources().             
+            status = Status.STATUS_MARKED_ROLLBACK;
+            return; // commit() will do rollback.
+         case Status.STATUS_ROLLEDBACK:
+            if (trace)
+               log.trace("Already rolledback, tx=" + toString());
+            return;
+         default:
+            throw new IllegalStateException("Cannot rollback(), tx=" +
+                                            toString() + ", status=" +
+                                            TxUtils.getStatusAsString(status));
+         }
+      }
+      finally
+      {
+         Thread.interrupted();// clear timeout that did an interrupt            
+         unlock();
+      }
+   }
+   
+   /**
+    * Forgets the heuristic status of the transaction. This method should
+    * be called on heuristically completed transactions. 
+    */
+   public void forget()
+   {
+      lock();
+      try {
+         if (heuristicCode == HEUR_NONE && !heuristicHazard)
+            return;
+         
+         if (status != Status.STATUS_COMMITTED &&
+               status != Status.STATUS_ROLLEDBACK &&
+               (status != Status.STATUS_COMMITTING || !heuristicHazard) &&
+               (status != Status.STATUS_ROLLING_BACK || !heuristicHazard))
+            return;
+
+         if (forgetResources())
+         {
+            // Clear heuristics status from stable storage 
+            RecoveryLogger logger = TxManager.getInstance().getRecoveryLogger();
+            if (logger != null)
+            {
+               logger.clearHeuristicStatus(xid.getLocalIdValue());
+               if (status == Status.STATUS_COMMITTED &&
+                     completionHandler != null)
+                  completionHandler.handleTxCompletion(xid.getLocalIdValue());
+            }
+            instanceDone();
+         }
+      }
+      finally
+      {
+         unlock();
+      }
+   }
+
+   /**
+    * If this transaction is in the committing state, this method starts
+    * a thread that calls commit on the remote resources that were not yet 
+    * successful committed. It returns the current status of the transaction
+    * (before any commit calls are issued).
+    * 
+    * @return the current status of the transaction.
+    */
+   public int replayCompletion(final Resource r)
+   {
+      lock();
+      try
+      {
+         if (status != Status.STATUS_MARKED_ROLLBACK &&
+               status != Status.STATUS_ROLLING_BACK &&
+               status != Status.STATUS_ROLLEDBACK)
+         {
+            final boolean pendingCommits = 
+               (status == Status.STATUS_COMMITTING && 
+                     remoteResourcesToRetry > 0);
+            
+            Runnable runnable =  new Runnable() 
+            {  
+               public void run()
+               {
+                  try
+                  {
+                     if (trace)
+                        log.trace("Replay completion thread: " + 
+                                  " committing remote resource " + r); 
+                     r.commit();
+                     
+                     if (trace)
+                        log.trace("Replay completion thread: " +
+                                  " committed remote resource " + r); 
+                  }
+                  catch (Throwable ignored)
+                  {
+                     if (trace)
+                        log.trace("Replay completion thread: ignored exception" 
+                                  + " when committing remote resource " + r , 
+                                  ignored);
+                  }
+                  
+                  if (pendingCommits)
+                  {
+                     lock();
+                     try
+                     {
+                        if (trace)
+                           log.trace("Replay completion thread: calling " +
+                                     "retryCommitRemoteResources");
+                        
+                        retryCommitRemoteResources();
+                        if (xaResourcesToRetry == 0 && 
+                              remoteResourcesToRetry == 0 && 
+                              heuristicCode == HEUR_NONE)
+                           completeTransaction();
+                     }
+                     finally
+                     {
+                        unlock();
+                     }
+                  }
+                  if (trace)
+                     log.trace("Replay completion thread: exiting");
+               }
+            };
+            Thread t = new Thread(runnable, "replayCompletionThread");
+            
+            t.start();
+         }
+         return status;
+      }
+      finally
+      {
+         unlock();
+      }      
+   }
+   
+   /**
+    * DTM/OTS servants need this method to implement the get transaction
+    * propagation context method of the coordinator interface.
+    *
+    * @param errorRollback throw a RollbackException if the transaction is 
+    *                      not active
+    * @return the time left before this transaction time-out expires.
+    * @throws RollbackException if the transaction is not active and 
+    *                           errorRollback is true.
+    */
+   public long getTimeLeftBeforeTimeout(boolean errorRollback) 
+           throws RollbackException
+   {
+      if (errorRollback && status != Status.STATUS_ACTIVE)
+         throw new RollbackException("Transaction is not active: " + 
+                                     TxUtils.getStatusAsString(status));
+      return (start + timeoutPeriod) - System.currentTimeMillis();
+   }
+
+   public int getAssociatedThreadCount()
+   {
+      lock();
+      try
+      {
+         return threads.size();
+      }
+      finally
+      {
+         unlock();
+      }
+   }
+
+   public Set getAssociatedThreads()
+   {
+      lock();
+      try
+      {
+         return Collections.unmodifiableSet(threads);
+      }
+      finally
+      {
+         unlock();
+      }
+   }
+
+   public int hashCode()
+   {
+      return xid.hashCode();
+   }
+
+   public String toString()
+   {
+      return "TransactionImpl:" + xidFactory.toString(xid);
+   }
+
+   public boolean equals(Object obj)
+   {
+      if (obj != null && obj instanceof TransactionImpl)
+         return getLocalIdValue() == (((TransactionImpl) obj).getLocalIdValue());
+      return false;
+   }
+
+
+   /**
+    * Returns the local id of this transaction. The local id is used as
+    * a transaction propagation context within the JBoss server, and
+    * in the TxManager for mapping local transaction ids to transactions.
+    */
+   public long getLocalIdValue()
+   {
+      return xid.getLocalIdValue();
+   }
+
+   /**
+    * Returns the local id of this transaction. The local id is used as
+    * a transaction propagation context within the JBoss server, and
+    * in the TxManager for mapping local transaction ids to transactions.
+    */
+   public LocalId getLocalId()
+   {
+      return xid.getLocalId();
+   }
+
+   /**
+    * Returns the global id of this transaction. Ths global id is used in
+    * the TxManager, which keeps a map from global ids to transactions.
+    */
+   public GlobalId getGlobalId()
+   {
+      return xid.getTrulyGlobalId();
+   }
+
+   /**
+    * Returns the xid of this transaction.
+    */
+   public XidImpl getXid()
+   {
+      return xid;
+   }
+
+   /**
+    * Returns this transaction's DTM propagation context.
+    */
+   public TxPropagationContext getPropagationContext()
+   {
+      if (dtmPropagationContext == null)
+      {
+         Coordinator coordinatorToPropagate;
+
+         if (parentCoordinator == null || interpositionEnabled ||
+             !Proxy.isProxyClass(parentCoordinator.getClass()))
+         {
+            // no parent coordinator, or interposition enabled, or
+            // parent coordinator is a wrapped OTS coordinator: 
+            // propagate proxy to a local DTM coordinator 
+            coordinatorToPropagate =
+            dtmCoordinatorFactory.createCoordinator(getLocalIdValue());
+         }
+         else
+         {
+            // parent coordinator is a DTM coordinator and interposition is 
+            // disabled: propagate the parent coordinator  
+            coordinatorToPropagate = parentCoordinator;
+         }
+
+         // Create TxPropagationContext
+         dtmPropagationContext =
+         new TxPropagationContext(xid.getFormatId(),
+                                  xid.getGlobalTransactionId(),
+                                  0, /* timeout set below */
+                                  coordinatorToPropagate,
+                                  null /* null Terminator */);
+      }
+      
+      // Update the timeout in the TPC and return the TPC 
+      try
+      {
+         dtmPropagationContext.timeout =
+            TMUtil.divideAndRoundUp(getTimeLeftBeforeTimeout(true), 1000);
+      }
+      catch (RollbackException transactionNotActive)
+      {
+         if (status == Status.STATUS_MARKED_ROLLBACK)
+            dtmPropagationContext.timeout = 0;
+         else
+            dtmPropagationContext = null;
+      }
+      return dtmPropagationContext;
+   }
+
+   /**
+    * Returns this transaction's OTS propagation context.
+    */
+   public Object getOTSPropagationContext()
+   {
+      if (otsPropagationContext == null)
+      {
+         if (parentCoordinator == null || interpositionEnabled ||
+             Proxy.isProxyClass(parentCoordinator.getClass()))
+         {
+            // no parent coordinator, or interposition enabled, or
+            // parent coordinator is a dynamic proxy (rather than a wrapped 
+            // OTS coordinator): propagate reference to a local OTS coordinator 
+            otsPropagationContext =
+            otsContextFactory.createOTSContext(xid.getFormatId(),
+                                               xid.getGlobalTransactionId(),
+                                               getLocalIdValue());
+         }
+         else
+         {
+            // parent coordinator is a DTM coordinator and interposition is 
+            // disabled: propagate the parent coordinator  
+            otsPropagationContext =
+            otsContextFactory.createOTSContext(xid.getFormatId(),
+                                               xid.getGlobalTransactionId(),
+                                               parentCoordinator);
+         }
+      }
+      
+      // Update the timeout in the TPC and return the TPC
+      try
+      {
+         otsContextFactory.setTimeout(
+               otsPropagationContext,
+               TMUtil.divideAndRoundUp(getTimeLeftBeforeTimeout(true), 1000));
+      }
+      catch (RollbackException transactionNotActive)
+      {
+         if (status == Status.STATUS_MARKED_ROLLBACK)
+            otsContextFactory.setTimeout(otsPropagationContext, 0);
+         else
+            otsPropagationContext = null;
+      }
+      return otsPropagationContext;
+   }
+   
+   public void setHeuristicStatus(LogRecord.HeurData heurData)
+   {
+      lock();
+      try
+      {
+         if (status != heurData.transactionStatus)
+         {
+            if (status == Status.STATUS_PREPARED)
+               cancelPreparedTimeout();
+            
+            status = heurData.transactionStatus;
+            
+            if (status == Status.STATUS_COMMITTING)
+            {
+               retryCommitRemoteResources();
+               
+               if (xaResourcesToRetry > 0)
+               {
+                  // Keep this TransactionImpl around to
+                  // commit its XAResources at a later time.
+                  commitXAResourcesAfterTimeout();
+               }
+               else if (remoteResourcesToRetry == 0 && heuristicCode == HEUR_NONE)
+               {
+                  // This TransactionImpl is not needed anymore. 
+                  completeTransaction();
+               }
+               else
+               {
+                  // Do nothing. Just keep this TransactionImpl around to 
+                  // receive replayCompletion calls from remote resources.
+               }
+            }
+            else if (status == Status.STATUS_ROLLING_BACK)
+            {
+               rollbackResourcesAndCompleteTransaction();
+            }
+         }
+      }
+      finally
+      {
+         unlock();
+      }
+   }
+   
+   // Package protected ---------------------------------------------
+
+   /**
+    * Checks whether this transaction is a foreign transaction that entered
+    * the server via JCA transaction inflow and is either in the prepared state
+    * or in a heuristically completed state.  
+    * 
+    * @return true if this is a JCA inbound transaction that is either prepared
+    *         or heuristically completed (but not yet forgotten), false 
+    *         otherwise.
+    */
+   boolean isPreparedOrHeuristicallyCompletedJCAInboundTx()
+   {
+      return inboundBranchQualifier != null 
+         && (status == Status.STATUS_PREPARED 
+               || (getFullHeuristicCode() != HEUR_NONE 
+                     && (status == Status.STATUS_COMMITTED 
+                           || status == Status.STATUS_ROLLEDBACK)));
+   }
+   
+   /**
+    * Gets the inbound <code>Xid</code> of a foreign transaction that entered 
+    * the server via JCA transaction inflow.
+    * 
+    * @return the inbound <code>Xid</code>.
+    */
+   Xid getInboundXid()
+   {
+      return new Xid() 
+      {
+         public int getFormatId()
+         {
+            return xid.getFormatId();
+         }
+
+         public byte[] getGlobalTransactionId()
+         {
+            return xid.getGlobalTransactionId();
+         }
+
+         public byte[] getBranchQualifier()
+         {
+            return (byte[])inboundBranchQualifier.clone();
+         }
+      };
+   }
+   
+   void associateCurrentThread()
+   {
+      Thread.interrupted();
+      lock();
+      try
+      {
+         threads.add(Thread.currentThread());
+      }
+      finally
+      {
+         unlock();
+      }
+   }
+
+   void disassociateCurrentThread()
+   {
+      // Just a tidyup, no need to synchronize
+      if (done)
+      {
+         threads.remove(Thread.currentThread());
+      }
+      else
+      {
+         // Removing the association for an active transaction
+         lock();
+         try
+         {
+            threads.remove(Thread.currentThread());
+         }
+         finally
+         {
+            unlock();
+         }
+      }
+      Thread.interrupted();
+   }
+
+   /**
+    * Lock this instance.
+    */
+   synchronized void lock()
+   {
+      if (done)
+         throw new IllegalStateException("Transaction has terminated, tx=" + 
+                                         toString());
+
+      Thread currentThread = Thread.currentThread();
+      if (locked != null && locked != currentThread)
+      {
+         log.debug("Lock contention, tx=" + toString() + 
+                   " otherThread=" + locked);
+         //DEBUG Thread.currentThread().dumpStack();
+
+         while (locked != null && locked != currentThread)
+         {
+            try
+            {
+               // Wakeup happens when:
+               // - notify() is called from unlock()
+               // - notifyAll is called from instanceDone()
+               wait();
+            }
+            catch (InterruptedException ex)
+            {
+               // ignore
+            }
+
+            if (done)
+               throw new IllegalStateException(
+                           "Transaction has now terminated, tx=" + toString());
+         }
+      }
+
+      locked = currentThread;
+      ++lockDepth;
+   }
+
+   /**
+    * Unlock this instance.
+    */
+   synchronized void unlock()
+   {
+      Thread currentThread = Thread.currentThread();
+      if (locked == null || locked != currentThread)
+      {
+         log.warn("Unlocking, but not locked, tx=" + toString() + 
+                  " otherThread=" + locked,
+                  new Throwable("[Stack trace]"));
+      }
+      else
+      {
+         if (--lockDepth == 0)
+         {
+            locked = null;
+            notify();
+         }
+      }
+   }
+   
+   /**
+    * Deactivate this transaction.
+    */
+   synchronized void deactivate()
+   {
+      lock();
+      try
+      {
+         cancelTimeout();
+         cancelPreparedTimeout();
+         cancelXARetryTimeout();
+      
+         // Clear tables refering to external objects.
+         // Even if a client holds on to this instance forever, the objects
+         // that we have referenced may be garbage collected.
+         parentCoordinator = null;
+         sync = null;
+         xaResources = null;
+         remoteResources = null;
+         transactionLocalMap.clear();
+         threads.clear();
+      
+         // Set the status
+         status = Status.STATUS_NO_TRANSACTION;
+
+         // Notify all threads waiting for the lock.
+         notifyAll();
+
+         // set the done flag
+         done = true;
+      }
+      finally
+      {
+         unlock();
+      }
+   }
+
+   /**
+    * Get the work
+    *
+    * @return the work
+    */
+   Work getWork()
+   {
+      return work;
+   }
+
+   /**
+    * Set the work
+    *
+    * @param work the work
+    * @throws WorkCompletedException with error code WorkException.TX_CONCURRENT_WORK_DISALLOWED
+    *                                when work is already present for the xid or whose completion is in progress, only
+    *                                the global part of the xid must be used for this check. Or with error code
+    *                                WorkException.TX_RECREATE_FAILED if it is unable to recreate the transaction context
+    */
+   void setWork(Work work)
+           throws WorkCompletedException
+   {
+      lock();
+      try
+      {
+         if (work == null)
+         {
+            this.work = null;
+            return;
+         }
+
+         if (status == Status.STATUS_NO_TRANSACTION || status == Status.STATUS_UNKNOWN)
+            throw new WorkCompletedException("The transaction is not active " +
+                                             toString() + ": " +
+                                             TxUtils.getStatusAsString(status),
+                                             WorkException.TX_RECREATE_FAILED);
+         else if (status != Status.STATUS_ACTIVE)
+            throw new WorkCompletedException("Too late to start work " + 
+                                             toString() + ": " + 
+                                             TxUtils.getStatusAsString(status),
+                                             WorkException.TX_CONCURRENT_WORK_DISALLOWED);
+         else if (this.work != null)
+            throw new WorkCompletedException("Already have work " + toString() +
+                                             ": " + this.work,
+                                             WorkException.TX_CONCURRENT_WORK_DISALLOWED);
+
+         this.work = work;
+      }
+      finally
+      {
+         unlock();
+      }
+   }
+
+   /**
+    * Getter for property done.
+    */
+   boolean isDone()
+   {
+      return done || (heuristicCode != HEUR_NONE &&
+                          (status == Status.STATUS_COMMITTING || 
+                           status == Status.STATUS_COMMITTED ||
+                           status == Status.STATUS_ROLLING_BACK ||
+                           status == Status.STATUS_ROLLEDBACK));
+   }
+
+   Object getTransactionLocalValue(TransactionLocal tlocal)
+   {
+      return transactionLocalMap.get(tlocal);
+   }
+
+   void putTransactionLocalValue(TransactionLocal tlocal, Object value)
+   {
+      transactionLocalMap.put(tlocal, value);
+   }
+
+   boolean containsTransactionLocal(TransactionLocal tlocal)
+   {
+      return transactionLocalMap.containsKey(tlocal);
+   }
+
+   // Private -------------------------------------------------------
+
+   /**
+    * Before prepare
+    */
+   private void beforePrepare()
+           throws HeuristicMixedException,
+                  HeuristicRollbackException,
+                  RollbackException
+   {
+      checkIntegrity();
+
+      doBeforeCompletion();
+
+      if (trace)
+         log.trace("Before completion done, tx=" + toString() +
+                   ", status=" + TxUtils.getStatusAsString(status));
+
+      endResources();
+   }
+
+   /**
+    * Check the integrity of the transaction
+    */
+   private void checkIntegrity() 
+         throws HeuristicMixedException, 
+                HeuristicRollbackException, 
+                RollbackException
+   {
+      // Spec defined checks for the transaction in a valid state
+      checkBeforeStatus();
+
+      TransactionIntegrity integrity = TxManager.getInstance().getTransactionIntegrity();
+      if (integrity != null)
+      {
+         // Extra integrity checks
+         unlock();
+         try
+         {
+            integrity.checkTransactionIntegrity(this);
+         }
+         finally
+         {
+            lock();
+         }
+         
+         // Recheck the transaction state
+         checkBeforeStatus();
+      }
+   }
+
+   /**
+    * Check the before status
+    */
+   private void checkBeforeStatus()
+      throws HeuristicMixedException,
+             HeuristicRollbackException,
+             RollbackException
+   {
+      switch (status)
+      {
+      case Status.STATUS_PREPARING:
+         throw new IllegalStateException("Already started preparing, tx=" + 
+                                         toString());
+      case Status.STATUS_PREPARED:
+         throw new IllegalStateException("Already prepared, tx=" + toString());
+      case Status.STATUS_ROLLING_BACK:
+         throw new IllegalStateException("Already started rolling back, tx=" +
+                                         toString());
+      case Status.STATUS_ROLLEDBACK:
+         instanceDone();
+         //checkHeuristics();
+         throw new IllegalStateException("Already rolled back, tx=" + 
+                                         toString());
+      case Status.STATUS_COMMITTING:
+         throw new IllegalStateException("Already started committing, tx=" + 
+                                         toString());
+      case Status.STATUS_COMMITTED:
+         instanceDone();
+         //checkHeuristics();
+         throw new IllegalStateException("Already committed, tx=" + toString());
+      case Status.STATUS_NO_TRANSACTION:
+         throw new IllegalStateException("No transaction, tx=" + toString());
+      case Status.STATUS_UNKNOWN:
+         throw new IllegalStateException("Unknown state, tx=" + toString());
+      case Status.STATUS_MARKED_ROLLBACK:
+         endResources();
+         rollbackResourcesAndCompleteTransaction();
+         checkHeuristicsButDoNotThrowHeuristicHazard();
+         throw new RollbackException("Already marked for rollback, tx=" + 
+                                     toString());
+      case Status.STATUS_ACTIVE:
+         break;
+      default:
+         throw new IllegalStateException("Illegal status: " +
+                                         TxUtils.getStatusAsString(status) +
+                                         ", tx=" + toString());
+      }
+   }
+   
+   /**
+    * Complete the transaction
+    */
+   private void completeTransaction()
+   {
+      cancelTimeout();
+      doAfterCompletion();
+      instanceDone();
+   }
+
+   /**
+    * Checks if the transaction state allows calls to the methods
+    * <code>enlistResource</code> and <code>registerSynchronization</code>
+    *
+    * @throws RollbackException
+    * @throws IllegalStateException
+    */
+   private void checkStatus()
+           throws RollbackException
+   {
+      switch (status)
+      {
+      case Status.STATUS_ACTIVE:
+      case Status.STATUS_PREPARING:
+         break;
+      case Status.STATUS_PREPARED:
+         throw new IllegalStateException("Already prepared, tx=" + toString());
+      case Status.STATUS_COMMITTING:
+         throw new IllegalStateException("Already started committing, tx=" +
+                                         toString());
+      case Status.STATUS_COMMITTED:
+         throw new IllegalStateException("Already committed, tx=" + toString());
+      case Status.STATUS_MARKED_ROLLBACK:
+         throw new RollbackException("Already marked for rollback, tx=" +
+                                     toString());
+      case Status.STATUS_ROLLING_BACK:
+         throw new RollbackException("Already started rolling back, tx=" +
+                                     toString());
+      case Status.STATUS_ROLLEDBACK:
+         throw new RollbackException("Already rolled back, tx=" + toString());
+      case Status.STATUS_NO_TRANSACTION:
+         throw new IllegalStateException("No transaction, tx=" + toString());
+      case Status.STATUS_UNKNOWN:
+         throw new IllegalStateException("Unknown state, tx=" + toString());
+      default:
+         throw new IllegalStateException("Illegal status: " +
+                                         TxUtils.getStatusAsString(status) +
+                                         ", tx=" + toString());
+      }
+   }
+
+   /**
+    * Interrupt all threads involved with transaction
+    * This is called on timeout
+    */
+   private void interruptThreads()
+   {
+      TxManager manager = TxManager.getInstance();
+      if (manager.isInterruptThreads())
+      {
+         HashSet clone = (HashSet) threads.clone();
+         threads.clear();
+         for (Iterator i = clone.iterator(); i.hasNext();)
+         {
+            Thread thread = (Thread) i.next();
+            try
+            {
+               thread.interrupt();
+            }
+            catch (Throwable ignored)
+            {
+               if (trace)
+                  log.trace("Ignored error interrupting thread: " +
+                            thread, ignored);
+            }
+         }
+      }
+   }
+
+   private void logXAException(XAException xae)
+   {
+      log.warn("XAException: tx=" + toString() + " errorCode=" +
+               TxUtils.getXAErrorCodeAsString(xae.errorCode), xae);
+      if (xaExceptionFormatter != null)
+         xaExceptionFormatter.formatXAException(xae, log);
+   }
+   
+   /**
+    * Mark this transaction as non-existing.
+    */
+   private synchronized void instanceDone()
+   {
+      TxManager manager = TxManager.getInstance();
+
+      if (status == Status.STATUS_COMMITTED)
+         manager.incCommitCount();
+      else
+         manager.incRollbackCount();
+
+      // Clear tables refering to external objects.
+      // Even if a client holds on to this instance forever, the objects
+      // that we have referenced may be garbage collected.
+      parentCoordinator = null;
+      sync = null;
+      xaResources = null;
+      remoteResources = null;
+      transactionLocalMap.clear();
+      threads.clear();
+      
+      // Garbage collection
+      manager.releaseTransactionImpl(this);
+
+      // Set the status
+      status = Status.STATUS_NO_TRANSACTION;
+
+      // Notify all threads waiting for the lock.
+      notifyAll();
+
+      // set the done flag
+      done = true;
+   }
+
+   /**
+    * Cancel the timeout.
+    * This will release the lock while calling out.
+    */
+   private void cancelTimeout()
+   {
+      if (timeout != null)
+      {
+         unlock();
+         try
+         {
+            timeout.cancel();
+         }
+         catch (Exception e)
+         {
+            if (trace)
+               log.trace("failed to cancel timeout, tx=" + toString(), e);
+         }
+         finally
+         {
+            lock();
+         }
+         timeout = null;
+      }
+   }
+
+   /**
+    * Create prepared timeout. 
+    */
+   private void createPreparedTimeout()
+   {
+      preparedTimeout = timeoutFactory.createTimeout(
+            System.currentTimeMillis() + 
+                  TxManager.getInstance().getPreparedTimeoutMillis(),
+            new TimeoutTarget()
+            {
+               public void timedOut(Timeout timeout)
+               {
+                  if (trace)
+                     log.trace("Prepared timeout expired for tx=" + 
+                               TransactionImpl.this.toString() +
+                               ", calling replayCompletion " +
+                               "on the recovery coordinator");
+                  try
+                  {
+                     recoveryCoordinator.replayCompletion(registeredResource);
+                  }
+                  catch (NoSuchObjectException noCoordinator)
+                  {
+                     if (trace)
+                     {
+                        log.trace("Exception in replayCompletion: " +
+                                  "no coordinator for tx=" + toString(), 
+                                  noCoordinator);
+                        log.trace("Rolling back transaction branch, tx=" + 
+                                  TransactionImpl.this.toString());
+                     }
+                     try
+                     {
+                        rollbackBranch();
+                     }
+                     catch (Exception e)
+                     {
+                        if (trace)
+                           log.trace("Exception in transaction branch rollback," 
+                                     + " tx=" + TransactionImpl.this.toString(),
+                                     e);
+                        
+                     }
+                  }
+                  catch (Exception ignore)
+                  {
+                     if (trace)
+                        log.trace("Ignoring exception in replayCompletion, tx=" 
+                                  + TransactionImpl.this.toString(), ignore);
+                  }
+                  if (!done)
+                  {
+                     lock();
+                     try
+                     {
+                        if (preparedTimeout != null)
+                        {
+                           preparedTimeout = timeoutFactory.createTimeout(
+                                 System.currentTimeMillis() + 
+                                       TxManager.getInstance()
+                                                .getPreparedTimeoutMillis(),
+                                 this);
+                        }
+                     }
+                     finally
+                     {
+                        unlock();
+                     }
+                  }
+               }
+            });
+   }
+
+   /**
+    * Cancel prepared timeout.
+    */
+   private void cancelPreparedTimeout()
+   {
+      if (preparedTimeout != null)
+      {
+         Timeout pt = preparedTimeout;
+         preparedTimeout = null;
+         
+         unlock();
+         try
+         {
+            pt.cancel();
+         }
+         catch (Exception e)
+         {
+            if (trace)
+               log.trace("failed to cancel prepared timeout, tx=" + toString(), 
+                         e);
+         }
+         finally
+         {
+            lock();
+         }
+      }
+   }
+   
+   /**
+    * Return the resource for the given XAResource
+    */
+   private EnlistedXAResource findResource(XAResource xaRes)
+   {
+      // A linear search may seem slow, but please note that
+      // the number of XA resources registered with a transaction
+      // are usually low.
+      // Note: This searches backwards intentionally!  It ensures that
+      // if this resource was enlisted multiple times, then the last one
+      // will be returned.  All others should be in the state RS_ENDED.
+      // This allows ResourceManagers that always return false from isSameRM
+      // to be enlisted and delisted multiple times.
+      for (int idx = xaResources.size() - 1; idx >= 0; --idx)
+      {
+         EnlistedXAResource resource =
+                 (EnlistedXAResource) xaResources.get(idx);
+         if (xaRes == resource.getXAResource())
+            return resource;
+      }
+
+      return null;
+   }
+
+   private EnlistedXAResource findResourceManager(XAResource xaRes)
+           throws XAException
+   {
+      for (int i = 0; i < xaResources.size(); ++i)
+      {
+         EnlistedXAResource resource = (EnlistedXAResource) xaResources.get(i);
+         if (resource.isResourceManager(xaRes))
+            return resource;
+      }
+      return null;
+   }
+
+   /**
+    * Add a resource, expanding tables if needed.
+    *
+    * @param xaRes          The new XA resource to add. It is assumed that the
+    *                       resource is not already in the table of XA resources.
+    * @param branchXid      The Xid for the transaction branch that is to
+    *                       be used for associating with this resource.
+    * @param sameRMResource The resource of the first
+    *                       XA resource having the same resource manager as
+    *                       <code>xaRes</code>, or <code>null</code> if <code>xaRes</code>
+    *                       is the first resource seen with this resource manager.
+    * @return the new resource
+    */
+   private EnlistedXAResource addResource(XAResource xaRes,
+                                          Xid branchXid,
+                                          EnlistedXAResource sameRMResource)
+   {
+      EnlistedXAResource resource =
+              new EnlistedXAResource(xaRes, branchXid, sameRMResource);
+      xaResources.add(resource);
+
+      // Remember the first resource that wants the last resource gambit
+      if (lastResource == null && xaRes instanceof LastResource)
+         lastResource = resource;
+
+      return resource;
+   }
+
+   /**
+    * End Tx association for all resources.
+    */
+   private void endResources()
+   {
+      for (int idx = 0; idx < xaResources.size(); ++idx)
+      {
+         EnlistedXAResource resource =
+                 (EnlistedXAResource) xaResources.get(idx);
+         try
+         {
+            resource.endResource();
+         }
+         catch (XAException xae)
+         {
+            logXAException(xae);
+            status = Status.STATUS_MARKED_ROLLBACK;
+            cause = xae;
+         }
+      }
+      resourcesEnded = true; // Too late to enlist new resources.
+   }
+
+
+   /**
+    * Call synchronization <code>beforeCompletion()</code>.
+    * This will release the lock while calling out.
+    */
+   private void doBeforeCompletion()
+   {
+      unlock();
+      try
+      {
+         for (int i = 0; i < syncCount; i++)
+         {
+            try
+            {
+               if (trace)
+                  log.trace("calling sync " + i + ", " + sync[i] +
+                            " tx=" + toString());
+
+               sync[i].beforeCompletion();
+            }
+            catch (Throwable t)
+            {
+               if (trace)
+                  log.trace("failed before completion " + sync[i], t);
+
+               status = Status.STATUS_MARKED_ROLLBACK;
+
+               // save the cause off so the user can inspect it
+               cause = t;
+               break;
+            }
+         }
+      }
+      finally
+      {
+         lock();
+      }
+   }
+
+   /**
+    * Call synchronization <code>afterCompletion()</code>.
+    * This will release the lock while calling out.
+    */
+   private void doAfterCompletion()
+   {
+      // Assert: Status indicates: Too late to add new synchronizations.
+      unlock();
+      try
+      {
+         for (int i = 0; i < syncCount; i++)
+         {
+            try
+            {
+               sync[i].afterCompletion(status);
+            }
+            catch (Throwable t)
+            {
+               if (trace)
+                  log.trace("failed after completion " + sync[i], t);
+            }
+         }
+      }
+      finally
+      {
+         lock();
+      }
+   }
+
+   /**
+    * Groups two heuristic codes into a composite code.
+    * 
+    * @param code1 a heuristic code (one of 
+    *              <code>XAException.XA_HEURxxx</code>)
+    * @param code2 another heuristic code
+    * @return the composite heuristic code.
+    */
+   private static int groupHeuristics(int code1, int code2)
+   {
+      switch (code2)
+      {
+      case XAException.XA_HEURMIX:
+         code1 = XAException.XA_HEURMIX;
+         break;
+      case XAException.XA_HEURRB:
+         if (code1 == HEUR_NONE)
+            code1 = XAException.XA_HEURRB;
+         else if (code1 == XAException.XA_HEURCOM ||
+                  code1 == XAException.XA_HEURHAZ)
+            code1 = XAException.XA_HEURMIX;
+         break;
+      case XAException.XA_HEURCOM:
+         if (code1 == HEUR_NONE)
+            code1 = XAException.XA_HEURCOM;
+         else if (code1 == XAException.XA_HEURRB ||
+                  code1 == XAException.XA_HEURHAZ)
+            code1 = XAException.XA_HEURMIX;
+         break;
+      case XAException.XA_HEURHAZ:
+         if (code1 == HEUR_NONE)
+            code1 = XAException.XA_HEURHAZ;
+         else if (code1 == XAException.XA_HEURCOM ||
+                  code1 == XAException.XA_HEURRB)
+            code1 = XAException.XA_HEURMIX;
+         break;
+      default:
+         throw new IllegalArgumentException();
+      }
+
+      return code1;
+   }
+
+   /**
+    * We got another heuristic.
+    * <p/>
+    * Promote <code>heuristicCode</code> if needed and remember to call forget
+    * on the resource at a later time, upon request from the coordinator
+    * or from a management application.
+    * 
+    * This will release the lock while calling out.
+    *
+    * @param resource  the resource of the XA resource that got a
+    *                  heuristic in our internal tables, or <code>null</code>
+    *                  if the heuristic came from here.
+    * @param code      the heuristic code, one of
+    *                  <code>XAException.XA_HEURxxx</code>.
+    */
+   private void gotHeuristic(EnlistedResource resource, int code)
+   {
+      heuristicCode = groupHeuristics(heuristicCode, code);
+      if (resource != null) 
+            resource.remember(code);
+   }
+   
+   /**
+    * Checks if a mixed decision happened and promote the
+    * <code>heuristicCode</code> in that case.
+    * 
+    * @return the <code>heuristicCode</code>.
+    */
+   private int consolidateHeuristics()
+   {
+      if (committedResources > 0 && rolledbackResources > 0)
+         heuristicCode = XAException.XA_HEURMIX;
+      
+      return heuristicCode;
+   }
+   
+   /**
+    * Gets the full heuristic code, which includes an heuristic hazard if this
+    * <code>TransactionImpl</code> could not reach a resource during the second
+    * phase of 2PC. 
+    *
+    * @return the full heuristic code, one of
+    *         <code>XAException.XA_HEURxxx</code>.
+    */
+   private int getFullHeuristicCode()
+   {
+      return (heuristicHazard) ?  
+                        groupHeuristics(heuristicCode, XAException.XA_HEURHAZ) : 
+                        heuristicCode;
+   }
+
+   /**
+    * Check for heuristics and throw exception if any found.
+    */
+   private void checkHeuristics()
+           throws HeuristicHazardException,
+                  HeuristicMixedException,
+                  HeuristicRollbackException
+   {
+      if (committedResources > 0 && rolledbackResources > 0)
+         heuristicCode = XAException.XA_HEURMIX;
+      
+      switch (getFullHeuristicCode())
+      {
+      case XAException.XA_HEURHAZ:
+         if (trace)
+            log.trace("Throwing HeuristicHazardException, tx=" + toString() +
+                      ", status=" + TxUtils.getStatusAsString(status));
+         throw new HeuristicHazardException();
+      case XAException.XA_HEURMIX:
+         if (trace)
+            log.trace("Throwing HeuristicMixedException, tx=" + toString() +
+                      ", status=" + TxUtils.getStatusAsString(status));
+         throw new HeuristicMixedException();
+      case XAException.XA_HEURRB:
+         if (trace)
+            log.trace("Throwing HeuristicRollbackException, tx=" + toString() +
+                      ", status=" + TxUtils.getStatusAsString(status));
+         throw new HeuristicRollbackException();
+      case XAException.XA_HEURCOM:
+         // Why isn't HeuristicCommitException used in JTA ?
+         // And why define something that is not used ?
+         if (trace)
+            log.trace("NOT Throwing HeuristicCommitException, tx=" + toString()
+                      + ", status=" + TxUtils.getStatusAsString(status));
+         return;
+      }
+   }
+
+   private void checkHeuristicsButDoNotThrowHeuristicHazard()
+         throws HeuristicMixedException,
+                HeuristicRollbackException
+   {
+      if (committedResources > 0 && rolledbackResources > 0)
+         heuristicCode = XAException.XA_HEURMIX;
+                   
+      switch (getFullHeuristicCode())
+      {
+      case XAException.XA_HEURHAZ:
+         if (TxManager.getInstance().isReportHeuristicHazardAsHeuristicMixed())
+         {
+            if (trace)
+               log.trace("Throwing HeuristicMixedException instead of " +
+                         "HeuristicHazardException, tx=" + toString() + 
+                         ", status=" + TxUtils.getStatusAsString(status));
+            throw new HeuristicMixedException();
+         }
+         else
+         {
+            if (trace)
+               log.trace("NOT throwing HeuristicHazardException, tx=" + 
+                         toString() + ", status=" + 
+                         TxUtils.getStatusAsString(status));
+            return;
+         }
+      case XAException.XA_HEURMIX:
+         if (trace)
+            log.trace("Throwing HeuristicMixedException, tx=" + toString() +
+                      ", status=" + TxUtils.getStatusAsString(status));
+         throw new HeuristicMixedException();
+      case XAException.XA_HEURRB:
+         if (trace)
+            log.trace("Throwing HeuristicRollbackException, tx=" + toString() +
+                      ", status=" + TxUtils.getStatusAsString(status));
+         throw new HeuristicRollbackException();
+      case XAException.XA_HEURCOM:
+         // Why isn't HeuristicCommitException used in JTA ?
+         // And why define something that is not used ?
+         if (trace)
+            log.trace("NOT Throwing HeuristicCommitException, tx=" + toString()
+                      + ", status=" + TxUtils.getStatusAsString(status));
+         return;
+      }
+   }
+  
+   /**
+    * Registers this transaction as a resource with its parent coordinator.
+    * Called from <code>enlistResource</code>.
+    *
+    * @throws RollbackException
+    * @throws java.lang.IllegalStateException
+    *
+    * @throws SystemException
+    */
+   private void registerResourceWithParentCoordinator()
+           throws RollbackException
+   {
+      if (trace)
+         log.trace("registerResourceWithParentCoordinator(): Entered, tx=" +
+                   toString() + ", status=" + TxUtils.getStatusAsString(status));
+      
+      Resource r = null;
+
+      if (Proxy.isProxyClass(parentCoordinator.getClass()))
+      {
+         // DTM coordinator case
+         if (dtmResourceFactory != null)
+            r = dtmResourceFactory.createResource(getLocalIdValue());
+         else
+         {
+            log.warn("Missing DTM resource factory, tx=" + toString());
+            status = Status.STATUS_MARKED_ROLLBACK;
+            throw new RollbackException("Missing DTM resource factory");
+         }
+      }
+      else
+      {
+         // OTS coordinator case
+         if (otsResourceFactory != null)
+            r = otsResourceFactory.createResource(getLocalIdValue());
+         else
+         {
+            log.warn("Missing OTS resource factory, tx=" + toString());
+            status = Status.STATUS_MARKED_ROLLBACK;
+            throw new RollbackException("Missing OTS resource factory");
+         }
+      }
+
+      try
+      {
+         unlock();
+         try
+         {
+            recoveryCoordinator = parentCoordinator.registerResource(r);
+            registeredResource = r;
+         }
+         finally
+         {
+            lock();
+         }
+      }
+      catch (TransactionInactiveException e)
+      {
+         status = Status.STATUS_MARKED_ROLLBACK;
+         if (trace)
+            log.trace("Got TransactionInactiveException, " +
+                      "throwing RollbackException");
+         throw new RollbackException(e.toString());
+      }
+      catch (TransactionRolledbackException e)
+      {
+         status = Status.STATUS_MARKED_ROLLBACK;
+         if (trace)
+            log.trace("Got TransactionRolledbackException, " +
+                      "throwing RollbackException");
+         throw new RollbackException(e.toString());
+      }
+      catch (RemoteException e)
+      {
+         status = Status.STATUS_MARKED_ROLLBACK;
+         if (trace)
+            log.trace("Got RemoteException, throwing RollbackException");
+         throw new RollbackException(e.toString());
+      }
+   }
+
+   /**
+    * Prepare all enlisted resources.
+    * If the first phase of the commit process results in a decision
+    * to commit the <code>status</code> will be
+    * <code>Status.STATUS_PREPARED</code> on return.
+    * Otherwise the <code>status</code> will be
+    * <code>Status.STATUS_MARKED_ROLLBACK</code> on return.
+    * This will release the lock while calling out.
+    *
+    * @return True iff all resources voted read-only.
+    */
+   private boolean prepareResources()
+   {
+      boolean readOnly = true;
+
+      status = Status.STATUS_PREPARING;
+      
+      // Prepare XA resources.
+      
+      if (trace)
+         log.trace("Preparing " + xaResources.size() + " XA resource(s)");
+
+      for (int i = 0; i < xaResources.size(); ++i)
+      {
+         // Abort prepare on state change.
+         if (status != Status.STATUS_PREPARING)
+            return false;
+
+         EnlistedXAResource resource = (EnlistedXAResource) xaResources.get(i);
+
+         if (resource.isResourceManager() == false)
+            continue; // This RM already prepared.
+
+         // Ignore the last resource it is done later
+         if (resource == lastResource)
+            continue;
+
+         try
+         {
+            int vote = resource.prepare();
+
+            if (trace)
+            {
+               String strVote = 
+                  (vote == RS_VOTE_OK) ? 
+                     "OK" : 
+                     ((vote == RS_VOTE_READONLY) ? "READONLY" : "ROLLBACK"); 
+
+               log.trace("XA resource voted " + strVote);
+            }
+
+            if (vote == RS_VOTE_OK)
+               readOnly = false;
+            else if (vote != RS_VOTE_READONLY)
+            {
+               // Neither commit vote nor readonly vote: rollback.
+               if (trace)
+                  log.trace("prepareResources got a rollback vote " +
+                            "from an XA resource, tx=" + toString() + 
+                            " resource=" + resource, new Exception());
+               status = Status.STATUS_MARKED_ROLLBACK;
+               return false;
+            }
+         }
+         catch (XAException e)
+         {
+            readOnly = false;
+            logXAException(e);
+            switch (e.errorCode)
+            {
+            case XAException.XA_HEURCOM:
+               // Heuristic commit is not that bad when preparing.
+               // But it means trouble if we have to rollback.
+               gotHeuristic(resource, e.errorCode);
+               break;
+            case XAException.XA_HEURRB:
+            case XAException.XA_HEURMIX:
+            case XAException.XA_HEURHAZ:
+               // Other heuristic exceptions cause a rollback
+               gotHeuristic(resource, e.errorCode);
+               // fall through
+            default:
+               cause = e;
+               status = Status.STATUS_MARKED_ROLLBACK;
+               break;
+            }
+         }
+         catch (Throwable t)
+         {
+            if (t instanceof RecoveryTestingException)
+               throw (RecoveryTestingException)t;
+            
+            if (trace)
+               log.trace("unhandled throwable in prepareResources " + 
+                         toString(), t);
+            status = Status.STATUS_MARKED_ROLLBACK;
+            cause = t;
+            return false;
+         }
+      }
+      
+      // Prepare remote DTM/OTS resources.
+
+      if (trace)
+         log.trace("Preparing " + remoteResources.size() + 
+                   " remote resource(s)");
+
+      for (int i = 0; i < remoteResources.size(); ++i)
+      {
+         // Abort prepare on state change.
+         if (status != Status.STATUS_PREPARING)
+            return false;
+
+         EnlistedRemoteResource resource =
+                 (EnlistedRemoteResource) remoteResources.get(i);
+
+         try
+         {
+            int vote = resource.prepare();
+
+            if (trace)
+            {
+               String strVote = 
+                  (vote == RS_VOTE_OK) ? 
+                     "OK" : 
+                     ((vote == RS_VOTE_READONLY) ? "READONLY" : "ROLLBACK"); 
+
+               log.trace("Remote resource voted " + strVote);
+            }
+
+            if (vote == RS_VOTE_OK)
+               readOnly = false;
+            else if (vote != RS_VOTE_READONLY)
+            {
+               // Neither commit vote nor readonly vote: rollback.
+               if (trace)
+                  log.trace("prepareResources got a rollback vote " +
+                            "from a remote resource, tx=" + toString() + 
+                            ", resource=" + resource, new Exception());
+               status = Status.STATUS_MARKED_ROLLBACK;
+               return false;
+            }
+         }
+         catch (Exception e)
+         {
+            // Blanket catch for the following exception types:
+            //   TransactionAlreadyPreparedException,
+            //   HeuristicMixedException, 
+            //   HeuristicHazardException, and
+            //   RemoteException, which includes TransactionRolledbackException.
+            if (trace)
+               log.trace("Exception in prepareResources, tx=" + toString(), e);
+
+            if (e instanceof HeuristicMixedException)
+               gotHeuristic(resource, XAException.XA_HEURMIX);
+            else if (e instanceof HeuristicHazardException)
+               gotHeuristic(resource, XAException.XA_HEURHAZ);
+
+            cause = e;
+            status = Status.STATUS_MARKED_ROLLBACK;
+         }
+      }
+      
+      // Abort prepare on state change.
+      if (status != Status.STATUS_PREPARING)
+         return false;
+      
+      // Are we doing the last resource gambit?
+      if (lastResource != null)
+      {
+         try
+         {
+            lastResource.prepareLastResource();
+            lastResource.commit(false);
+         }
+         catch (XAException e)
+         {
+            if (trace)
+               log.trace("prepareResources got XAException" +
+                         " when committing last resource, tx=" + toString(), e);
+            logXAException(e);
+            switch (e.errorCode)
+            {
+               // No need to handle the case of XAException.XA_HEURCOM, 
+               // which is swallowed by EnlistedXAResource.commit() 
+            case XAException.XA_HEURRB:
+            case XAException.XA_HEURMIX:
+            case XAException.XA_HEURHAZ:
+               //usually throws an exception, but not for a couple of cases.
+               gotHeuristic(lastResource, e.errorCode);
+               // fall through
+            default:
+               // take the XAException as a "no" vote from the last resource
+               status = Status.STATUS_MARKED_ROLLBACK;
+               cause = e;
+               return false; // to make the caller look at
+               // at the "marked rollback" status
+            }
+         }
+         catch (Throwable t)
+         {
+            if (trace)
+               log.trace("unhandled throwable in prepareResources, tx=" + 
+                         toString(), t);
+            status = Status.STATUS_MARKED_ROLLBACK;
+            cause = t;
+            return false; // to make the caller look at
+            // the "marked rollback" status
+         }
+      }
+
+      if (status == Status.STATUS_PREPARING)
+         status = Status.STATUS_PREPARED;
+      else
+         return false;
+
+      return readOnly;
+   }
+
+   /**
+    * Commit all enlisted resources.
+    * This will release the lock while calling out.
+    */
+   private void commitResources(boolean onePhase)
+   {
+      status = Status.STATUS_COMMITTING;
+      doCommitResources(onePhase);
+      if (xaResourcesToRetry > 0 || remoteResourcesToRetry > 0)
+      {
+         int retryLimit = TxManager.getInstance().getCompletionRetryLimit();
+         for (int n = 0;
+              n < retryLimit && 
+                 (xaResourcesToRetry > 0 || remoteResourcesToRetry > 0);
+              n++)
+         {
+            sleep(TxManager.getInstance().getCompletionRetryTimeoutMillis());
+            doCommitResources(onePhase);
+         } 
+      }
+      checkCommitCompletion();
+   }
+
+   /**
+    * Do the actual resource commiting. 
+    */
+   private void doCommitResources(boolean onePhase)
+   {
+      xaResourcesToRetry = 0;
+      remoteResourcesToRetry = 0;
+      
+      // Commit XA resources. 
+      for (int i = 0; i < xaResources.size(); ++i)
+      {
+
+         // Abort commit on state change.
+         if (status != Status.STATUS_COMMITTING)
+            return;
+
+         EnlistedXAResource resource =
+                 (EnlistedXAResource) xaResources.get(i);
+
+         // Ignore the last resource, it is already committed
+         if (onePhase == false && lastResource == resource)
+            continue;
+
+         try
+         {
+            resource.commit(onePhase);
+         }
+         catch (XAException e)
+         {
+            logXAException(e);
+            switch (e.errorCode)
+            {
+               // No need to handle the case of XAException.XA_HEURCOM, 
+               // which is swallowed by EnlistedXAResource.commit() 
+            case XAException.XA_HEURRB:
+            case XAException.XA_HEURMIX:
+            case XAException.XA_HEURHAZ:
+               //usually throws an exception, but not for a couple of cases.
+               gotHeuristic(resource, e.errorCode);
+               if (onePhase)
+                  status = Status.STATUS_MARKED_ROLLBACK;
+               break;
+            case XAException.XAER_RMERR:
+               // Not much we can do if there is an RMERR in the
+               // commit phase of 2pc. I guess we take it as a heuristic 
+               // rollback and try the other RMs.
+               gotHeuristic(null, XAException.XA_HEURRB);
+               if (onePhase)
+                  status = Status.STATUS_MARKED_ROLLBACK;
+               break;
+            case XAException.XAER_RMFAIL:
+            case XAException.XA_RETRY:
+               if (onePhase)
+                  status = Status.STATUS_MARKED_ROLLBACK;
+               else
+                  // The XAResource remains prepared. Retry the commit later.
+                  xaResourcesToRetry++;
+               break;
+            case XAException.XAER_NOTA:
+            case XAException.XAER_INVAL:
+            case XAException.XAER_PROTO:
+            default:
+               // This should never happen!
+               cause = e;
+               if (onePhase)
+                  status = Status.STATUS_MARKED_ROLLBACK;
+               else
+                  log.warn("Could not recover from unexpected XAException: " +
+                           "tx=" + toString() + " errorCode=" + 
+                           TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+               break;
+            }
+         }
+         catch (Throwable t)
+         {
+            if (t instanceof RecoveryTestingException)
+               throw (RecoveryTestingException) t;
+            if (trace)
+               log.trace("Unhandled throwable in doCommitResources " + 
+                         toString(), t);
+         }
+      }
+
+      // Commit remote DTM/OTS resources. 
+      for (int i = 0; i < remoteResources.size(); ++i)
+      {
+         // Abort commit on state change.
+         if (status != Status.STATUS_COMMITTING)
+            return;
+
+         EnlistedRemoteResource resource =
+                 (EnlistedRemoteResource) remoteResources.get(i);
+
+         try
+         {
+            resource.commit(onePhase);
+         }
+         catch (TransactionRolledbackException e)
+         {
+            if (trace)
+               log.trace("Exception in doCommitResources, tx=" + toString(), e);
+
+            if (onePhase)
+               status = Status.STATUS_MARKED_ROLLBACK;
+            else
+            {
+               // The resource decided to rollback in the second phase of 2PC:
+               // this is a heuristic outcome.
+               gotHeuristic(null, XAException.XA_HEURRB);
+            }
+         }
+         catch (HeuristicRollbackException e)
+         {
+            if (trace)
+               log.trace("Exception in doCommitResources, tx=" + toString(), e);
+
+            gotHeuristic(resource, XAException.XA_HEURRB);
+            if (onePhase)
+               status = Status.STATUS_MARKED_ROLLBACK;
+         }
+         catch (HeuristicMixedException e)
+         {
+            if (trace)
+               log.trace("Exception in doCommitResources, tx=" + toString(), e);
+
+            gotHeuristic(resource, XAException.XA_HEURMIX);
+            if (onePhase)
+               status = Status.STATUS_MARKED_ROLLBACK;
+         }
+         catch (HeuristicHazardException e)
+         {
+            if (trace)
+               log.trace("Exception in doCommitResources, tx=" + toString(), e);
+
+            gotHeuristic(resource, XAException.XA_HEURHAZ);
+            if (onePhase)
+               status = Status.STATUS_MARKED_ROLLBACK;
+         }
+         catch (TransactionNotPreparedException e)
+         {
+            // This should never happen!
+            cause = e;
+            if (trace)
+               log.trace("Exception in doCommitResources, tx=" + toString(), e);
+            if (onePhase)
+               status = Status.STATUS_MARKED_ROLLBACK;
+            else
+               log.warn("Could not recover from unexpected exception in " + 
+                        "doCommitResources: tx=" + toString() + 
+                        " exception=" + e);
+         }
+         catch (NoSuchObjectException e)
+         {
+            if (trace)
+               log.trace("Exception in doCommitResources, tx=" + toString(), e);
+            if (onePhase)
+               status = Status.STATUS_MARKED_ROLLBACK;
+            else
+            {
+               // Do nothing -- we must be doing crash recovery and the
+               // remote resource has been committed before the crash.
+               log.warn("Ignoring NoSuchObjectException in doCommitResources: " 
+                        + " tx=" + toString() + " exception=" + e);
+            }
+         }
+         catch (RemoteException e)
+         {
+            if (trace)
+               log.trace("Exception in doCommitResources, tx=" + toString(), e);
+            if (onePhase)
+               status = Status.STATUS_MARKED_ROLLBACK;
+            else
+            {
+               // The remote resource remains prepared. Retry the commit later.
+               remoteResourcesToRetry++;
+            }
+         }
+      }
+   }
+   
+   /**
+    * Create retry timeout for calling retryCommitXAResources(). 
+    */
+   private void commitXAResourcesAfterTimeout()
+   {
+      xaRetryTimeout = timeoutFactory.createTimeout(
+         System.currentTimeMillis() + 
+               TxManager.getInstance().getXARetryTimeoutMillis(),
+         new TimeoutTarget()
+         {
+            public void timedOut(Timeout timeout)
+            {
+               if (trace)
+                  log.trace("XA retry timeout expired for tx=" + 
+                            TransactionImpl.this.toString() +
+                            ", retrying commit on XAResources");
+               lock();
+               try
+               {
+                  retryCommitXAResources();
+                  if (xaResourcesToRetry > 0)
+                  {
+                     xaRetryTimeout = timeoutFactory.createTimeout(
+                        System.currentTimeMillis() + 
+                              TxManager.getInstance().getXARetryTimeoutMillis(),
+                        this);
+                  }
+                  else if (remoteResourcesToRetry == 0 && 
+                        heuristicCode == HEUR_NONE)
+                  {
+                     completeTransaction();
+                  }
+               }
+               finally
+               {
+                  unlock();
+               }
+            }
+         });
+   }
+
+   // Pre-condition: this TransactionImpl instance is locked.
+   // Post-condition: this TransactionImpl instance remains locked.
+   private void retryCommitXAResources()
+   {
+      if (trace)
+         log.trace("Retrying commit XA resources, tx=" + toString() +
+                   ", status=" + TxUtils.getStatusAsString(status) +
+                   ", xaResourcesToRetry=" + xaResourcesToRetry);
+
+      xaResourcesToRetry = 0;
+        
+      for (int i = 0; i < xaResources.size(); ++i)
+      {
+         EnlistedXAResource resource =
+                 (EnlistedXAResource) xaResources.get(i);
+
+         // Ignore the last resource, it is already committed
+         if (lastResource == resource)
+            continue;
+
+         try
+         {
+            resource.commit(false);
+         }
+         catch (XAException e)
+         {
+            logXAException(e);
+            switch (e.errorCode)
+            {
+               // No need to handle the case of XAException.XA_HEURCOM, 
+               // which is swallowed by EnlistedXAResource.commit() 
+            case XAException.XA_HEURRB:
+            case XAException.XA_HEURMIX:
+            case XAException.XA_HEURHAZ:
+               //usually throws an exception, but not for a couple of cases.
+               gotHeuristic(resource, e.errorCode);
+               break;
+            case XAException.XAER_RMERR:
+               // Not much we can do if there is an RMERR in the
+               // commit phase of 2pc. I guess we take it as a heuristic 
+               // rollback and try the other RMs.
+               gotHeuristic(null, XAException.XA_HEURRB);
+               break;
+            case XAException.XAER_RMFAIL:
+            case XAException.XA_RETRY:
+               // The XAResource remains prepared. Retry the commit later.
+               xaResourcesToRetry++;
+               break;
+            case XAException.XAER_NOTA:
+            case XAException.XAER_INVAL:
+            case XAException.XAER_PROTO:
+            default:
+               // This should never happen!
+               log.warn("Could not recover from unexpected XAException: " +
+                        "tx=" + toString() + " errorCode=" + 
+                         TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+               break;
+            }
+         }
+         catch (Throwable t)
+         {
+            if (t instanceof RecoveryTestingException)
+               throw (RecoveryTestingException) t;
+            if (trace)
+               log.trace("unhandled throwable in retryCommitXAResources " + 
+                         this, t);
+         }
+      }
+      
+      if (trace)
+         log.trace("Finished retrying commit XA resources, tx=" + toString() +
+                   ", status=" + TxUtils.getStatusAsString(status) +
+                   ", xaResourcesToRetry=" + xaResourcesToRetry);
+
+      checkCommitCompletion();
+   }
+   
+   // Pre-condition: this TransactionImpl instance is locked.
+   // Post-condition: this TransactionImpl instance remains locked.
+   private void retryCommitRemoteResources()
+   {
+      if (trace)
+         log.trace("Retrying commit remote resources, tx=" + toString() +
+                   ", status=" + TxUtils.getStatusAsString(status) +
+                   ", " + remoteResources.size() + " remote resources");
+
+      remoteResourcesToRetry = 0;
+      
+      // Commit remote DTM/OTS resources. 
+      for (int i = 0; i < remoteResources.size(); ++i)
+      {
+         EnlistedRemoteResource resource =
+                 (EnlistedRemoteResource) remoteResources.get(i);
+
+         try
+         {
+            resource.commit(false);
+         }
+         catch (HeuristicRollbackException e)
+         {
+            if (trace)
+               log.trace("Exception in retryCommitRemoteResources, tx=" + 
+                         toString(), e);
+
+            gotHeuristic(resource, XAException.XA_HEURRB);
+         }
+         catch (HeuristicMixedException e)
+         {
+            if (trace)
+               log.trace("Exception in retryCommitRemoteResources, tx=" + 
+                         toString(), e);
+
+            gotHeuristic(resource, XAException.XA_HEURMIX);
+         }
+         catch (HeuristicHazardException e)
+         {
+            if (trace)
+               log.trace("Exception in retryCommitRemoteResources, tx=" + 
+                         toString(), e);
+
+            gotHeuristic(resource, XAException.XA_HEURHAZ);
+         }
+         catch (TransactionNotPreparedException e)
+         {
+            // This should never happen!
+            cause = e;
+            if (trace)
+               log.trace("Exception in retryCommitRemoteResources, tx=" + 
+                         toString(), e);
+            log.warn("Could not recover from unexpected exception in " + 
+                     "retryCommitRemoteResources: tx=" + toString() + 
+                     " exception=" + e);
+         }
+         catch (NoSuchObjectException e)
+         {
+            if (trace)
+               log.trace("Exception in retryCommitRemoteResources, tx=" + 
+                         toString(), e);
+            
+            // Do nothing -- we must be doing crash recovery and the
+            // remote resource has been committed before the crash.
+            log.warn("Ignoring NoSuchObjectException in " + 
+                     "retryCommitRemoteResources: tx=" + toString() + 
+                     " exception=" + e);
+         }
+         catch (RemoteException e)
+         {
+            if (trace)
+               log.trace("Exception in retryCommitRemoteResources, tx=" + 
+                         toString(), e);
+            
+            // The remote resource remains prepared. Retry the commit later.
+            remoteResourcesToRetry++;
+         }
+      }
+
+      if (trace)
+         log.trace("Finished retrying commit remote resources, tx=" + toString() 
+                   + ", status=" + TxUtils.getStatusAsString(status) +
+                   ", remoteResourcesToRetry=" + remoteResourcesToRetry +
+                   " of " + remoteResources.size() + " remote resources");
+
+      checkCommitCompletion();
+   }
+
+   private void checkCommitCompletion()
+   {
+      if (status != Status.STATUS_COMMITTING)
+         return;
+      
+      consolidateHeuristics();
+
+      if (xaResourcesToRetry == 0 && remoteResourcesToRetry == 0)
+      {
+         status = Status.STATUS_COMMITTED;
+         if (heuristicHazard || heuristicCode != HEUR_NONE)
+         {
+            heuristicHazard = false;
+            
+            // Save the heuristic status to stable storage. Do not include
+            // a locally-detected heuristic hazard, as the actual heuristic
+            // outcome is known at this point.
+            RecoveryLogger logger = TxManager.getInstance().getRecoveryLogger();
+            if (logger != null)
+               logger.saveHeuristicStatus(xid.getLocalIdValue(),
+                                          foreignTx,
+                                          xid.getFormatId(),
+                                          xid.getGlobalTransactionId(),
+                                          inboundBranchQualifier,
+                                          status,
+                                          heuristicCode,
+                                          heuristicHazard,
+                                          getXAResourceHeuristics(),
+                                          getRemoteResourceHeuristics());
+            if (!foreignTx && 
+                  !TxManager.getInstance().isRootBranchRemembersHeuristicDecisions() && 
+                  forgetResources())
+            {
+               // Clear heuristic status
+               if (logger != null)
+               {
+                  long localId = xid.getLocalIdValue();
+                  logger.clearHeuristicStatus(localId);
+                  if (completionHandler != null)
+                     completionHandler.handleTxCompletion(localId);
+               }
+            }
+         }
+         else if (completionHandler != null)
+            completionHandler.handleTxCompletion(xid.getLocalIdValue());
+      }
+      else if (!heuristicHazard)
+      {
+         heuristicHazard = true;
+
+         // Save the heuristic status to stable storage, including the
+         // locally-detected heuristic hazard.
+         RecoveryLogger logger = TxManager.getInstance().getRecoveryLogger();
+         if (logger != null)
+            logger.saveHeuristicStatus(xid.getLocalIdValue(),
+                                       foreignTx,
+                                       xid.getFormatId(),
+                                       xid.getGlobalTransactionId(),
+                                       inboundBranchQualifier,
+                                       status,
+                                       heuristicCode,
+                                       heuristicHazard,
+                                       getXAResourceHeuristics(),
+                                       getRemoteResourceHeuristics());
+         
+         if (!foreignTx &&
+               !TxManager.getInstance().isRootBranchRemembersHeuristicDecisions())
+            forgetResources();
+      }
+   }
+   
+   /**
+    * Rollback all enlisted resources and complete the transaction.
+    * This will release the lock while calling out.
+    */
+   private void rollbackResourcesAndCompleteTransaction()
+   {
+      status = Status.STATUS_ROLLING_BACK;
+      doRollbackResources();
+      if (xaResourcesToRetry > 0 || remoteResourcesToRetry > 0)
+      {
+         int retryLimit = TxManager.getInstance().getCompletionRetryLimit();
+         for (int n = 0;
+              n < retryLimit && 
+                 (xaResourcesToRetry > 0 || remoteResourcesToRetry > 0);
+              n++)
+         {
+            sleep(TxManager.getInstance().getCompletionRetryTimeoutMillis());
+            doRollbackResources();
+         } 
+      }
+      
+      if (xaResourcesToRetry == 0 && remoteResourcesToRetry == 0)
+      {
+         status = Status.STATUS_ROLLEDBACK;
+         
+         if (consolidateHeuristics() != HEUR_NONE || heuristicHazard)
+         {
+            heuristicHazard = false;
+
+            // Save the heuristic status to stable storage. Do not include
+            // a locally-generated heuristic hazard, as the actual heuristic
+            // outcome is known at this point.
+            RecoveryLogger logger = TxManager.getInstance().getRecoveryLogger();
+            if (logger != null)
+               logger.saveHeuristicStatus(xid.getLocalIdValue(),
+                                          foreignTx,
+                                          xid.getFormatId(),
+                                          xid.getGlobalTransactionId(),
+                                          inboundBranchQualifier,
+                                          status,
+                                          heuristicCode,
+                                          heuristicHazard,
+                                          getXAResourceHeuristics(),
+                                          getRemoteResourceHeuristics());
+            if (!foreignTx && 
+                  !TxManager.getInstance().isRootBranchRemembersHeuristicDecisions() && 
+                  forgetResources())
+            {
+               // Clear heuristic status
+               if (logger != null)
+               {
+                  long localId = xid.getLocalIdValue();
+                  logger.clearHeuristicStatus(localId);
+               }
+            }
+         }
+         else
+            completeTransaction();
+      }
+      else
+      {
+         if (!heuristicHazard) 
+         {
+            heuristicHazard = true;
+            consolidateHeuristics();
+                  
+            // Save the heuristic status to stable storage, including the
+            // locally-detected heuristic hazard.
+            RecoveryLogger logger = TxManager.getInstance().getRecoveryLogger();
+            if (logger != null)
+               logger.saveHeuristicStatus(xid.getLocalIdValue(),
+                                          foreignTx,
+                                          xid.getFormatId(),
+                                          xid.getGlobalTransactionId(),
+                                          inboundBranchQualifier,
+                                          status,
+                                          heuristicCode,
+                                          heuristicHazard,
+                                          getXAResourceHeuristics(),
+                                          getRemoteResourceHeuristics());
+         }
+         if (xaResourcesToRetry > 0)
+            rollbackXAResourcesAfterTimeout();
+      }
+   }
+   
+   /**
+    * Do the actual resource rolling back. 
+    */
+   private void doRollbackResources()
+   {
+      xaResourcesToRetry = 0;
+      remoteResourcesToRetry = 0;
+
+      // Rollback XA resources.
+      for (int i = 0; i < xaResources.size(); ++i)
+      {
+         EnlistedXAResource resource = (EnlistedXAResource) xaResources.get(i);
+         try
+         {
+            resource.rollback();
+         }
+         catch (XAException e)
+         {
+            logXAException(e);
+            switch (e.errorCode)
+            {
+               // No need to handle the case of XAException.XA_HEURRB, 
+               // which is swallowed by EnlistedXAResource.rollback() 
+            case XAException.XA_HEURCOM:
+            case XAException.XA_HEURMIX:
+            case XAException.XA_HEURHAZ:
+               gotHeuristic(resource, e.errorCode);
+               continue;
+            default:
+               cause = e;
+               break;
+            }
+         }
+         catch (Throwable t)
+         {
+            if (t instanceof RecoveryTestingException)
+               throw (RecoveryTestingException) t;
+            if (trace)
+               log.trace("unhandled throwable in doRollbackResources " + this, 
+                         t);
+         }
+         if (resource.getState() == RS_VOTE_OK)
+            xaResourcesToRetry++;
+      }
+
+      // Rollback remote DTM/OTS resources.
+      for (int i = 0; i < remoteResources.size(); ++i)
+      {
+         EnlistedRemoteResource resource =
+                 (EnlistedRemoteResource) remoteResources.get(i);
+
+         try
+         {
+            resource.rollback();
+         }
+         catch (HeuristicCommitException e)
+         {
+            gotHeuristic(resource, XAException.XA_HEURCOM);
+            continue;
+         }
+         catch (HeuristicMixedException e)
+         {
+            gotHeuristic(resource, XAException.XA_HEURMIX);
+            continue;
+         }
+         catch (HeuristicHazardException e)
+         {
+            gotHeuristic(resource, XAException.XA_HEURHAZ);
+            continue;
+         }
+         catch (RemoteException e)
+         {
+            // No need to do much here. If the resource didn't get the
+            // rollback then it will eventually call replay completion on
+            // the recovery coordinator. We increase remoteResourcesToRetry
+            // for heuristic hazard reporting purposes only. 
+            remoteResourcesToRetry++;
+            if (trace)
+               log.trace("Ignoring exception in remote resource rollback, tx=" + 
+                         toString(), e);
+         }
+      }
+   }
+   
+   /**
+    * Create timeout for calling retryRollbackXAResources(). 
+    */
+   private void rollbackXAResourcesAfterTimeout()
+   {
+      xaRetryTimeout = timeoutFactory.createTimeout(
+         System.currentTimeMillis() + 
+               TxManager.getInstance().getXARetryTimeoutMillis(),
+         new TimeoutTarget()
+         {
+            public void timedOut(Timeout timeout)
+            {
+               if (trace)
+                  log.trace("XA retry timeout expired for tx=" + 
+                            TransactionImpl.this.toString() +
+                            ", retrying rollback on XAResources");
+               lock();
+               try
+               {
+                  retryRollbackXAResources();
+                  if (xaResourcesToRetry > 0)
+                  {
+                     xaRetryTimeout = timeoutFactory.createTimeout(
+                        System.currentTimeMillis() + 
+                              TxManager.getInstance().getXARetryTimeoutMillis(), 
+                        this);
+                  }
+                  else if (remoteResourcesToRetry == 0 && 
+                        heuristicCode == HEUR_NONE) 
+                  {
+                     completeTransaction();
+                  }
+               }
+               finally
+               {
+                  unlock();
+               }
+            }
+         });
+   }
+      
+   /**
+    * Cancel XA retry timeout.
+    */
+   private void cancelXARetryTimeout()
+   {
+      if (xaRetryTimeout != null)
+      {
+         Timeout rt = xaRetryTimeout;
+         xaRetryTimeout = null;
+         
+         unlock();
+         try
+         {
+            rt.cancel();
+         }
+         catch (Exception e)
+         {
+            if (trace)
+               log.trace("failed to cancel XA retry timeout, tx=" + toString(),
+                         e);
+         }
+         finally
+         {
+            lock();
+         }
+      }
+   }
+   
+   private int[] getXAResourceHeuristics()
+   {
+      if (xaResourcesWithHeuristicDecisions == null)
+         return null;
+      else
+      {
+         int[] heurCodes = new int[xaResourcesWithHeuristicDecisions.size()]; 
+         for (int i = 0; i < xaResourcesWithHeuristicDecisions.size(); ++i)
+         {
+            EnlistedResource resource =
+               (EnlistedResource) xaResourcesWithHeuristicDecisions.get(i);
+            heurCodes[i] = resource.getHeuristicCode();
+         }
+         return heurCodes;
+      }
+   }
+   
+   private HeuristicStatus[] getRemoteResourceHeuristics()
+   {
+      if (remoteResourcesWithHeuristicDecisions == null)
+         return null;
+      else
+      {
+         HeuristicStatus s[] = 
+            new HeuristicStatus[remoteResourcesWithHeuristicDecisions.size()];
+         for (int i = 0; i < remoteResourcesWithHeuristicDecisions.size(); ++i)
+         {
+            EnlistedRemoteResource resource =
+               (EnlistedRemoteResource) remoteResourcesWithHeuristicDecisions.get(i);
+            s[i] = new HeuristicStatus(
+                  resource.getHeuristicCode(),
+                  resourceToString(resource.getRemoteResource()));
+         }
+         return s;
+      }
+   }
+   
+   
+   private void retryRollbackXAResources()
+   {
+      xaResourcesToRetry = 0;
+
+      // Rollback XA resources.
+      for (int i = 0; i < xaResources.size(); ++i)
+      {
+         EnlistedXAResource resource = (EnlistedXAResource) xaResources.get(i);
+         try
+         {
+            resource.rollback();
+         }
+         catch (XAException e)
+         {
+            logXAException(e);
+            switch (e.errorCode)
+            {
+               // No need to handle the case of XAException.XA_HEURRB, 
+               // which is swallowed by EnlistedXAResource.rollback() 
+            case XAException.XA_HEURCOM:
+            case XAException.XA_HEURMIX:
+            case XAException.XA_HEURHAZ:
+               gotHeuristic(resource, e.errorCode);
+               continue;
+            default:
+               cause = e;
+               break;
+            }
+         }
+         catch (Throwable t)
+         {
+            if (t instanceof RecoveryTestingException)
+               throw (RecoveryTestingException) t;
+            if (trace)
+               log.trace("unhandled throwable in retryRollbackXAResources " +
+                         this, t);
+         }
+         if (resource.getState() == RS_VOTE_OK)
+            xaResourcesToRetry++;
+      }
+
+      if (xaResourcesToRetry == 0 && remoteResourcesToRetry == 0)   
+         status = Status.STATUS_ROLLEDBACK;
+   }
+   
+   private boolean forgetResources()
+   {
+      boolean success = true;
+      if (xaResourcesWithHeuristicDecisions != null)
+      {
+         for (int i = 0; i < xaResourcesWithHeuristicDecisions.size(); ++i)
+         {
+            EnlistedResource resource =
+               (EnlistedResource) xaResourcesWithHeuristicDecisions.get(i);
+            resource.forget();
+            if (resource.getState() != RS_FORGOT)
+               success = false; 
+         }
+      }
+
+      if (remoteResourcesWithHeuristicDecisions != null)
+      {
+         for (int i = 0; i < remoteResourcesWithHeuristicDecisions.size(); ++i)
+         {
+            EnlistedResource resource =
+               (EnlistedResource) remoteResourcesWithHeuristicDecisions.get(i);
+            resource.forget();
+            if (resource.getState() != RS_FORGOT)
+               success = false; 
+         }
+      }
+      return success;
+   }
+   
+   /**
+    * Create an Xid representing a new branch of this transaction.
+    */
+   private Xid createXidBranch()
+   {
+      long branchId = ++lastBranchId;
+
+      return xidFactory.newBranch(xid, branchId);
+   }
+
+   /**
+    * Determine the commit strategy
+    *
+    * @return 0 for nothing to do, 1 for one phase and 2 for two phase
+    */
+   private int getCommitStrategy()
+   {
+      int xaResourceCount = xaResources.size();
+      int remoteResourceCount = remoteResources.size();
+      int resourceCount = xaResourceCount + remoteResourceCount;
+
+      if (resourceCount == 0)
+         return 0;
+
+      if (resourceCount == 1)
+         return 1;
+
+      // At least two resources have participated in this transaction.
+      
+      if (remoteResourceCount > 0)
+      {
+         // They cannot be all XA resources on the same RM, 
+         // as at least one of them is a remote DTM/OTS resource.
+         return 2;
+      }
+      else  // They are all XA resources, look if the're all on the same RM.
+      {
+         // First XAResource surely isResourceManager, it's the first!
+         for (int i = 1; i < xaResourceCount; ++i)
+         {
+            EnlistedXAResource resource = (EnlistedXAResource) xaResources.get(i);
+            if (resource.isResourceManager())
+            {
+               // this one is not the same rm as previous ones,
+               // there must be at least 2
+               return 2;
+            }
+
+         }
+         // All RMs are the same one, one phase commit is ok.
+         return 1;
+      }
+   }
+
+   /**
+   * Gets stringfied references for all enlisted resources that voted commit.
+   * 
+   * @return an array of stringfied <code>Resource</code> references.
+   */
+   private String[] getStringfiedRemoteResourcesThatVotedCommit()
+   {
+      List resourcesThatVotedCommit = new ArrayList(remoteResources.size());
+      
+      for (int i = 0; i < remoteResources.size(); ++i)
+      {
+         EnlistedRemoteResource enlistedRemoteResource =
+                 (EnlistedRemoteResource) remoteResources.get(i);
+         if (enlistedRemoteResource.getState() == RS_VOTE_OK)
+         {
+            Resource r = enlistedRemoteResource.getRemoteResource();
+            resourcesThatVotedCommit.add(resourceToString(r));
+         }
+      }
+      return (String[]) resourcesThatVotedCommit.toArray(
+                              new String[resourcesThatVotedCommit.size()]);
+   }
+
+   /**
+    * Check we have no outstanding work
+    *
+    * @throws IllegalStateException when there is still work
+    */
+   private void checkWork()
+   {
+      if (work != null)
+         throw new IllegalStateException("Work still outstanding: " + work +
+                                         ", tx=" + toString());
+   }
+
+   /**
+    * Waits (blocking the caller) for the specified number of milliseconds. 
+    * 
+    * @param millis the length of time to sleep in milliseconds.
+    */
+   private void sleep(long millis)
+   {
+      try
+      {
+         unlock();
+         Thread.sleep(millis);
+      }
+      catch (InterruptedException e)
+      {
+         // ignore
+      }
+      finally
+      {
+         lock();
+      }
+   }
+   
+   // Inner classes -------------------------------------------------
+   
+   /**
+    * Represents a resource enlisted in the transaction.
+    * The resource can be either an XA resource or a remote DTM/OTS resource.
+    */
+   private interface EnlistedResource
+   {
+      public int getState();
+      public void remember(int heuristicCode);
+      int getHeuristicCode();
+      public void forget();
+   }
+
+   /**
+    * Represents an XA resource enlisted in the transaction
+    */
+   private class EnlistedXAResource
+           implements EnlistedResource
+   {
+      /**
+       * The XAResource
+       */
+      private XAResource xaResource;
+
+      /**
+       * The state of the resources
+       */
+      private int resourceState;
+
+      /**
+       * The related XA resource from the same resource manager
+       */
+      private EnlistedXAResource resourceSameRM;
+
+      /**
+       * The Xid of this resource
+       */
+      private Xid resourceXid;
+      
+      /**
+       * The heuristic status of this resource
+       */
+      private int heurCode;
+
+      /**
+       * The XAResourceAccess instance to be released after this 
+       * EnlistedXAResource is committed or rolled back, or null if no
+       * XAResourceAccess instance needs to be released.
+       */
+      private XAResourceAccess xaResourceAccess = null;
+
+      /**
+       * Create a new resource
+       */
+      public EnlistedXAResource(XAResource xaResource, 
+                                Xid resourceXid, 
+                                EnlistedXAResource resourceSameRM)
+      {
+         this.xaResource = xaResource;
+         this.resourceXid = resourceXid;
+         this.resourceSameRM = resourceSameRM;
+         resourceState = RS_NEW;
+         heurCode = HEUR_NONE;
+      }
+
+      /**
+       * Creates a prepared resource. 
+       * This constructor is intended to be used during recovery only.
+       */
+      public EnlistedXAResource(XAWork xaWork)
+      {
+         this.xaResource = xaWork.res;
+         this.resourceXid = xaWork.xid;
+         this.xaResourceAccess = xaWork.xaResourceAccess;
+         this.resourceSameRM = null;
+         resourceState = RS_VOTE_OK;
+         heurCode = HEUR_NONE;
+      }
+
+      /**
+       * Creates a resource in a heuristically completed state. 
+       * This constructor is intended to be used during recovery only.
+       */
+      public EnlistedXAResource(XAResource xaResource, 
+                                Xid resourceXid, 
+                                boolean committed)
+      {
+         this.xaResource = xaResource;
+         this.resourceXid = resourceXid;
+         this.resourceSameRM = null;
+         this.resourceState = (committed) ? RS_COMMITTED: RS_ROLLEDBACK;
+         heurCode = HEUR_UNKNOWN;
+      }
+
+      /**
+       * Get the XAResource for this resource
+       */
+      public XAResource getXAResource()
+      {
+         return xaResource;
+      }
+
+      /**
+       * Get the Xid for this resource
+       */
+      public Xid getXid()
+      {
+         return resourceXid;
+      }
+      
+      /**
+       * Gets the state of the XAResource represented by this
+       * <code>EnlistedXAResource</code> instance.
+       */
+      public int getState()
+      {
+         return resourceState;
+      }
+
+      /**
+       * Is the resource enlisted?
+       */
+      public boolean isEnlisted()
+      {
+         return resourceState == RS_ENLISTED;
+      }
+
+      /**
+       * Is this a resource manager
+       */
+      public boolean isResourceManager()
+      {
+         return resourceSameRM == null;
+      }
+
+      /**
+       * Is this the resource manager for the passed XA resource
+       */
+      public boolean isResourceManager(XAResource xaRes)
+              throws XAException
+      {
+         return resourceSameRM == null && xaRes.isSameRM(xaResource);
+      }
+
+      /**
+       * Is the resource delisted and the XAResource always returns false
+       * for isSameRM
+       */
+      public boolean isDelisted(XAResource xaRes)
+              throws XAException
+      {
+         return resourceState == RS_ENDED && xaResource.isSameRM(xaRes) == false;
+      }
+
+      /**
+       * Call <code>start()</code> on a XAResource and update
+       * internal state information.
+       * This will release the lock while calling out.
+       *
+       * @return true when started, false otherwise
+       */
+      public boolean startResource()
+              throws XAException
+      {
+         int flags = XAResource.TMJOIN;
+
+         if (resourceSameRM == null)
+         {
+            switch (resourceState)
+            {
+            case RS_NEW:
+               flags = XAResource.TMNOFLAGS;
+               break;
+            case RS_SUSPENDED:
+               flags = XAResource.TMRESUME;
+               break;
+
+            default:
+               if (trace)
+                  log.trace("Unhandled resource state: " + resourceState +
+                            " (not RS_NEW or RS_SUSPENDED, using TMJOIN flags)");
+            }
+         }
+
+         if (trace)
+            log.trace("startResource(" +
+                      xidFactory.toString(resourceXid) +
+                      ") entered: " + xaResource.toString() +
+                      " flags=" + flags);
+
+         unlock();
+         // OSH FIXME: resourceState could be incorrect during this callout.
+         try
+         {
+            try
+            {
+               xaResource.start(resourceXid, flags);
+            }
+            catch (XAException e)
+            {
+               throw e;
+            }
+            catch (Throwable t)
+            {
+               if (trace)
+                  log.trace("unhandled throwable error in startResource",
+                            t);
+               status = Status.STATUS_MARKED_ROLLBACK;
+               return false;
+            }
+
+            // Now the XA resource is associated with a transaction.
+            resourceState = RS_ENLISTED;
+         }
+         finally
+         {
+            lock();
+            if (trace)
+               log.trace("startResource(" +
+                         xidFactory.toString(resourceXid) +
+                         ") leaving: " + xaResource.toString() +
+                         " flags=" + flags);
+         }
+         return true;
+      }
+
+      /**
+       * Delist the resource unless we already did it
+       */
+      public boolean delistResource(XAResource xaRes, int flag)
+              throws XAException
+      {
+         if (isDelisted(xaRes))
+         {
+            // This RM always returns false on isSameRM.  Further,
+            // the last resource has already been delisted.
+            log.warn("Resource already delisted.  tx=" + 
+                     TransactionImpl.this.toString());
+            return false;
+         }
+         endResource(flag);
+         return true;
+      }
+
+      /**
+       * End the resource
+       */
+      public void endResource()
+              throws XAException
+      {
+         if (resourceState == RS_ENLISTED || resourceState == RS_SUSPENDED)
+         {
+            if (trace)
+               log.trace("endresources(" + xaResource + "): state=" +
+                         resourceState);
+            endResource(XAResource.TMSUCCESS);
+         }
+      }
+
+      /**
+       * Call <code>end()</code> on the XAResource and update
+       * internal state information.
+       * This will release the lock while calling out.
+       *
+       * @param flag The flag argument for the end() call.
+       */
+      private void endResource(int flag)
+              throws XAException
+      {
+         if (trace)
+            log.trace("endResource(" +
+                      xidFactory.toString(resourceXid) +
+                      ") entered: " + xaResource.toString() +
+                      " flag=" + flag);
+
+         unlock();
+         // OSH FIXME: resourceState could be incorrect during this callout.
+         try
+         {
+            try
+            {
+               xaResource.end(resourceXid, flag);
+            }
+            catch (XAException e)
+            {
+               throw e;
+            }
+            catch (Throwable t)
+            {
+               if (trace)
+                  log.trace("unhandled throwable error in endResource", t);
+               status = Status.STATUS_MARKED_ROLLBACK;
+               // Resource may or may not be ended after illegal exception.
+               // We just assume it ended.
+               resourceState = RS_ENDED;
+               return;
+            }
+
+            // Update our internal state information
+            if (flag == XAResource.TMSUSPEND)
+               resourceState = RS_SUSPENDED;
+            else
+            {
+               if (flag == XAResource.TMFAIL)
+                  status = Status.STATUS_MARKED_ROLLBACK;
+               resourceState = RS_ENDED;
+            }
+         }
+         finally
+         {
+            lock();
+            if (trace)
+               log.trace("endResource(" +
+                         xidFactory.toString(resourceXid) +
+                         ") leaving: " + xaResource.toString() +
+                         " flag=" + flag);
+         }
+      }
+
+      /**
+       * Remember that this resource has a heuristic outcome.
+       */
+      public void remember(int heuristicCode)
+      {
+         heurCode = heuristicCode;
+         if (xaResourcesWithHeuristicDecisions == null)
+            xaResourcesWithHeuristicDecisions = new ArrayList();
+         xaResourcesWithHeuristicDecisions.add(this);
+      }
+      
+      /**
+       * Get the heuristic status of this resource.
+       */
+      public int getHeuristicCode()
+      {
+         return heurCode;
+      }
+      
+      /**
+       * Forget the resource
+       */
+      public void forget()
+      {
+         unlock();
+         if (trace)
+            log.trace("Forget: " + xaResource +
+                      " xid=" + xidFactory.toString(resourceXid));
+         try
+         {
+            xaResource.forget(resourceXid);
+            resourceState = RS_FORGOT;
+         }
+         catch (XAException xae)
+         {
+            logXAException(xae);
+            cause = xae;
+            if (xae.errorCode == XAException.XAER_NOTA)
+            {
+               if (trace)
+                  log.trace("XAER_NOTA in forget: " + xaResource +
+                            " xid=" + xidFactory.toString(resourceXid) + 
+                            "\nAssuming that the xid has been previously " +
+                            "forgotten.", xae);
+               resourceState = RS_FORGOT;
+            }
+         }
+         finally
+         {
+            lock();
+         }
+      }
+
+      /**
+       * Prepare the resource
+       */
+      public int prepare()
+              throws XAException
+      {
+         int vote;
+         unlock();
+         if (trace)
+            log.trace("Prepare: " + xaResource +
+                      " xid=" + xidFactory.toString(resourceXid));
+         try
+         {
+            vote = xaResource.prepare(resourceXid);
+            
+            if (vote == XAResource.XA_OK)
+               resourceState = RS_VOTE_OK;
+            else if (vote == XAResource.XA_RDONLY)
+               resourceState = RS_VOTE_READONLY;
+         }
+         catch (XAException e)
+         {
+            if (e.errorCode >= XAException.XA_RBBASE 
+                  && e.errorCode <= XAException.XA_RBEND)
+            {
+               if (trace)
+                  log.trace("Got rollback vote from XAResource " + xaResource +
+                            " xid=" + xidFactory.toString(resourceXid) +
+                            ", tx=" + TransactionImpl.this.toString() + 
+                            ", errorCode=" +
+                            TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+
+               resourceState = RS_ROLLEDBACK;
+            }
+            else
+               throw e;
+         }
+         finally
+         {
+            lock();
+         }
+         return resourceState;
+      }
+
+      /**
+       * Prepare the last resource
+       */
+      public void prepareLastResource()
+              throws XAException
+      {
+         resourceState = RS_VOTE_OK;
+      }
+
+      /**
+       * Commit the resource
+       */
+      public void commit(boolean onePhase)
+              throws XAException
+      {
+         if (!onePhase && resourceState != RS_VOTE_OK)
+            return; // Voted read-only at prepare phase.
+
+         if (resourceSameRM != null)
+            return; // This RM already committed.
+
+         unlock();
+         if (trace)
+            log.trace("Commit: " + xaResource +
+                      " xid=" + xidFactory.toString(resourceXid) +
+                      " onePhase=" + onePhase);
+         try
+         {
+            xaResource.commit(resourceXid, onePhase);
+            committedResources++;
+            resourceState = RS_COMMITTED;
+         }
+         catch (XAException e)
+         {
+            switch (e.errorCode)
+            {
+            case XAException.XA_HEURCOM:
+               // Ignore this exception, as the heuristic outcome
+               // is the one we wanted anyway.
+               logXAException(e);
+               if (trace)
+                  log.trace("Ignoring XAException.XA_HEURCOM" +
+                            " in XAResource.commit: " + xaResource +
+                            " xid=" + xidFactory.toString(resourceXid) +
+                            " onePhase=" + onePhase);
+               forget();
+               committedResources++;
+               resourceState = RS_COMMITTED;
+               break;
+            case XAException.XAER_RMERR:
+            case XAException.XA_HEURRB:
+               rolledbackResources++;
+               // fall through
+            case XAException.XA_HEURMIX:
+            case XAException.XA_HEURHAZ:
+               resourceState = RS_HEUR_OUTCOME;
+               throw e;
+            case XAException.XAER_RMFAIL:
+            case XAException.XA_RETRY:
+               // stay in RS_VOTE_OK state - the commit will be retried
+               throw e;
+            case XAException.XAER_NOTA:
+            case XAException.XAER_INVAL:
+            case XAException.XAER_PROTO:
+            default:
+               // no point retrying
+               resourceState = RS_ERROR;
+               throw e;
+            }
+         }
+         finally
+         {
+            if (xaResourceAccess != null && resourceState != RS_VOTE_OK)
+               xaResourceAccess.release();
+            lock();
+         }
+      }
+
+      /**
+       * Rollback the resource
+       */
+      public void rollback()
+              throws XAException
+      {
+         if (resourceState == RS_VOTE_READONLY || 
+               resourceState == RS_ROLLEDBACK || resourceState == RS_FORGOT)
+            return;
+
+         if (resourceSameRM != null)
+            return; // This RM already rolled back.
+
+         unlock();
+         if (trace)
+            log.trace("Rollback: " + xaResource +
+                      " xid=" + xidFactory.toString(resourceXid));
+         try
+         {
+            xaResource.rollback(resourceXid);
+            rolledbackResources++;
+            resourceState = RS_ROLLEDBACK;
+         }
+         catch (XAException e)
+         {
+            switch (e.errorCode)
+            {
+            case XAException.XAER_RMERR:
+               // Ignore this exception, as the transaction has been 
+               // rolled back, which is what we wanted anyway.
+               if (trace)
+                  log.trace("Ignoring XAException.XAER_RMERR" +
+                            " in XAResource.rollback: " + xaResource +
+                            " xid=" + xidFactory.toString(resourceXid));
+               rolledbackResources++;
+               resourceState = RS_ROLLEDBACK;
+               break;
+            case XAException.XA_HEURRB:
+               // Ignore this exception, as the heuristic outcome
+               // is the one we wanted anyway.
+               if (trace)
+                  log.trace("Ignoring XAException.XA_HEURRB" +
+                            " in XAResource.rollback: " + xaResource +
+                            " xid=" + xidFactory.toString(resourceXid));
+               forget();
+               rolledbackResources++;
+               resourceState = RS_ROLLEDBACK;
+               break;
+            case XAException.XA_HEURCOM:
+               committedResources++;
+            case XAException.XA_HEURMIX:
+            case XAException.XA_HEURHAZ:
+               resourceState = RS_HEUR_OUTCOME;
+               throw e;
+            case XAException.XAER_RMFAIL:
+            case XAException.XA_RETRY:
+               // stay in RS_VOTE_OK state - the rollback will be retried
+               throw e;
+            case XAException.XAER_NOTA:
+            case XAException.XAER_INVAL:
+            case XAException.XAER_PROTO:
+            default:
+               // no point retrying
+               resourceState = RS_ERROR;
+               throw e;
+            }
+         }
+         finally
+         {
+            if (xaResourceAccess != null && resourceState != RS_VOTE_OK)
+               xaResourceAccess.release();
+            lock();
+         }
+      }
+   }
+
+   /**
+    * Represents a remote resource enlisted in the transaction.
+    */
+   private class EnlistedRemoteResource
+           implements EnlistedResource
+   {
+      /**
+       * The remote resource.
+       */
+      private Resource remoteResource;
+
+      /**
+       * The state of the resource.
+       */
+      private int resourceState;
+      
+      /**
+       * The heuristic status of this resource
+       */
+      private int heurCode;
+
+      /**
+       * Creates a new <code>EnlistedRemoteResource</code>.
+       */
+      public EnlistedRemoteResource(Resource remoteResource)
+      {
+         this.remoteResource = remoteResource;
+         resourceState = RS_NEW;
+         heurCode = HEUR_NONE;
+      }
+
+      /**
+       * Creates a new <code>EnlistedRemoteResource</code>.
+       * This constructor is intended to be used during recovery only.
+       */
+      public EnlistedRemoteResource(Resource remoteResource, boolean prepared)
+      {
+         this.remoteResource = remoteResource;
+         if (prepared)
+            resourceState = RS_VOTE_OK;
+         else
+            resourceState = RS_NEW;
+         heurCode = HEUR_NONE;
+      }
+
+      /**
+       * Creates a resource in a heuristically completed state. 
+       * This constructor is intended to be used during recovery only.
+       */
+      public EnlistedRemoteResource(Resource remoteResource, 
+                                    boolean committed,
+                                    int heurCode)
+      {
+         this.remoteResource = remoteResource;
+         resourceState = (committed) ? RS_COMMITTED : RS_ROLLEDBACK;
+         this.heurCode = heurCode; 
+      }
+      
+      /**
+       * Gets the remote resource represented by this
+       * <code>EnlistedRemoteResource</code> instance.
+       */
+      public Resource getRemoteResource()
+      {
+         return remoteResource;
+      }
+
+      /**
+       * Gets the state of the remote resource represented by this
+       * <code>EnlistedRemoteResource</code> instance.
+       */
+      public int getState()
+      {
+         return resourceState;
+      }
+
+      /**
+       * Prepare the resource
+       */
+      public int prepare()
+              throws RemoteException,
+                     TransactionAlreadyPreparedException,
+                     HeuristicMixedException,
+                     HeuristicHazardException
+      {
+         Vote vote = null;
+         unlock();
+         try
+         {
+            vote = remoteResource.prepare();
+         }
+         finally
+         {
+            lock();
+         }
+
+         if (vote == Vote.COMMIT)
+            resourceState = RS_VOTE_OK;
+         else if (vote == Vote.READONLY)
+            resourceState = RS_VOTE_READONLY;
+         else /* (vote == Vote.ROLLBACK) */
+            resourceState = RS_ROLLEDBACK;
+
+         return resourceState;
+      }
+
+      /**
+       * Commit the resource
+       *
+       * @throws RemoteException
+       * @throws HeuristicHazardException
+       * @throws HeuristicMixedException
+       * @throws HeuristicRollbackException
+       * @throws TransactionNotPreparedException
+       *
+       */
+      public void commit(boolean onePhase)
+              throws RemoteException,
+                     TransactionNotPreparedException,
+                     HeuristicRollbackException,
+                     HeuristicMixedException,
+                     HeuristicHazardException
+      {
+         if (trace)
+            log.trace("Committing resource " + remoteResource +
+                      " state=" + resourceState);
+
+         if (!onePhase && resourceState != RS_VOTE_OK)
+            return; // Voted read-only at prepare phase.
+
+         unlock();
+         try
+         {
+            if (onePhase)
+               remoteResource.commitOnePhase();
+            else
+               remoteResource.commit();
+
+            committedResources++;
+            resourceState = RS_COMMITTED;
+         }
+         catch (TransactionRolledbackException e)
+         {
+            rolledbackResources++;
+            resourceState = RS_ROLLEDBACK;
+            throw e;
+         }
+         catch (RemoteException e)
+         {
+            // stay in RS_VOTE_OK state - the commit will be retried
+            throw e;
+         }
+         catch (TransactionNotPreparedException e)
+         {
+            resourceState = RS_ERROR;
+            throw e;
+         }
+         catch (HeuristicRollbackException e)
+         {
+            rolledbackResources++;
+            resourceState = RS_HEUR_OUTCOME;
+            throw e;
+         }
+         catch (HeuristicMixedException e)
+         {
+            resourceState = RS_HEUR_OUTCOME;
+            throw e;            
+         }
+         catch (HeuristicHazardException e)
+         {
+            resourceState = RS_HEUR_OUTCOME;
+            throw e;            
+         }
+         finally
+         {
+            lock();
+         }
+      }
+
+      /**
+       * Rollback the resource
+       */
+      public void rollback()
+              throws RemoteException,
+                     HeuristicCommitException,
+                     HeuristicMixedException,
+                     HeuristicHazardException
+      {
+         if (resourceState == RS_VOTE_READONLY || 
+               resourceState == RS_ROLLEDBACK || resourceState == RS_FORGOT)
+            return;
+
+         unlock();
+         try
+         {
+            remoteResource.rollback();
+            rolledbackResources++;
+            resourceState = RS_ROLLEDBACK;
+         }
+         catch (TransactionRolledbackException e)
+         {
+            rolledbackResources++;
+            resourceState = RS_ROLLEDBACK;
+            throw e;
+         }
+         catch (RemoteException e)
+         {
+            // stay in RS_VOTE_OK state - the rollback will be retried
+            throw e;
+         }
+         catch (HeuristicCommitException e)
+         {
+            committedResources++;
+            resourceState = RS_HEUR_OUTCOME;
+            throw e;            
+         }
+         catch (HeuristicMixedException e)
+         {
+            resourceState = RS_HEUR_OUTCOME;
+            throw e;            
+         }
+         catch (HeuristicHazardException e)
+         {
+            resourceState = RS_HEUR_OUTCOME;
+            throw e;            
+         }
+         finally
+         {
+            lock();
+         }
+      }
+
+      /**
+       * Remember that this resource has a heuristic outcome.
+       */
+      public void remember(int heuristicCode)
+      {
+         heurCode = heuristicCode;
+         if (remoteResourcesWithHeuristicDecisions == null)
+            remoteResourcesWithHeuristicDecisions = new ArrayList();
+         remoteResourcesWithHeuristicDecisions.add(this);
+      }
+      
+      /**
+       * Get the heuristic status of this resource.
+       */
+      public int getHeuristicCode()
+      {
+         return heurCode;
+      }
+      
+      /**
+       * Forget the resource
+       */
+      public void forget()
+      {
+         unlock();
+         if (trace)
+            log.trace("Forget: " + remoteResource);
+         try
+         {
+            remoteResource.forget();
+            resourceState = RS_FORGOT;
+         }
+         catch (NoSuchObjectException e)
+         {
+            if (trace)
+               log.trace("NoSuchObjectException in forget: remoteResource=" + 
+                         remoteResource + "\nAssuming that the resource has " +
+                         "been previously forgotten.", e);
+         }
+         catch (RemoteException e)
+         {
+            if (trace)
+               log.trace("RemoteException in forget: remoteResource=" + 
+                         remoteResource + "\nThe resource still needs to be " +
+                         "forgotten.", e);
+         }
+         finally
+         {
+            lock();
+         }
+         resourceState = RS_FORGOT;
+      }
+
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/TransactionManagerInitializer.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/TransactionManagerInitializer.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/TransactionManagerInitializer.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,692 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.Name;
+import javax.naming.Reference;
+import javax.naming.spi.ObjectFactory;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAException;
+import org.jboss.logging.Logger;
+import org.jboss.tm.integrity.TransactionIntegrityFactory;
+import org.jboss.tm.recovery.RecoveryLogger;
+
+/**
+ *  This is a JMX service which manages the TransactionManager.
+ *  The service creates it and binds a Reference to it into JNDI.
+ *
+ *  @see TxManager
+ *  @author <a href="mailto:rickard.oberg at telkel.com">Rickard Oberg</a>
+ *  @author <a href="mailto:osh at sparre.dk">Ole Husgaard</a>
+ *  @author <a href="mailto:toby.allsopp at peace.com">Toby Allsopp</a>
+ *  @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ *  @version $Revision: 37459 $
+ *
+ * @jmx.mbean extends="org.jboss.system.ServiceMBean"
+ */
+public class TransactionManagerInitializer implements XAExceptionFormatter, ObjectFactory
+{
+   private static Logger log = Logger.getLogger(TransactionManagerInitializer.class);
+
+   // Constants -----------------------------------------------------
+   public static String JNDI_NAME = "java:/TransactionManager";
+   public static String JNDI_IMPORTER = "java:/TransactionPropagationContextImporter";
+   public static String JNDI_EXPORTER = "java:/TransactionPropagationContextExporter";
+
+   // Attributes ----------------------------------------------------
+
+   private boolean globalIdsEnabled = false; // flag duplicated in TM
+
+   /** Whether to interrupt threads at transaction timeout */
+   private boolean interruptThreads = false;
+
+   private int timeout = 300; // default tx timeout, dupl. in TM when it exists.
+
+   private int completionRetryLimit = 0;
+   
+   private int completionRetryTimeout = 1;
+   
+   private int xaRetryTimeout;
+
+   private int preparedTimeout;
+   
+   private boolean rootBranchRemembersHeuristicDecisions = true;
+   
+   private boolean reportHeuristicHazardAsHeuristicMixed = false;
+
+   private final Map xaExceptionFormatters = new HashMap();
+
+   private RecoveryLogger recoveryLogger;
+
+   private XidFactoryBase xidFactory;
+
+   private TransactionIntegrityFactory integrityFactory;
+
+   private InitialContext initialContext;
+
+   protected Hashtable initialContextProperties;
+
+   // Static --------------------------------------------------------
+
+   static TxManager tm;
+
+   // ServiceMBeanSupport overrides ---------------------------------
+
+   public void setInitialContextProperties(Hashtable initialContextProperties)
+   {
+      this.initialContextProperties = initialContextProperties;
+   }
+
+
+   public void start() throws Exception
+   {
+      if (initialContextProperties == null) initialContext = new InitialContext();
+      else initialContext = new InitialContext(initialContextProperties);
+
+      TransactionImpl.xidFactory = xidFactory;
+      TransactionImpl.xaExceptionFormatter = this;
+
+      // Get a reference to the TxManager singleton.
+      tm = TxManager.getInstance();
+      // Set its default timeout.
+      tm.setDefaultTransactionTimeout(timeout);
+      // Set the TxManager limit and timout for retrying a completion operation
+      // before reporting a heuristic hazard outcome.
+      tm.setCompletionRetryLimit(completionRetryLimit);
+      tm.setCompletionRetryTimeout(completionRetryTimeout);
+      // Set the TxManager timouts for retrying a XA operation and 
+      // for a foreign transaction to remain in the prepared state. 
+      tm.setXARetryTimeout(xaRetryTimeout);
+      tm.setPreparedTimeout(preparedTimeout);
+      // Set the TxManager flags for remembering heuristic decisions 
+      // at the root branch and for heuristic hazard reporting.
+      tm.setRootBranchRemembersHeuristicDecisions(
+                                       rootBranchRemembersHeuristicDecisions);
+      tm.setReportHeuristicHazardAsHeuristicMixed(
+                                       reportHeuristicHazardAsHeuristicMixed);
+      // Initialize its globalIdsEnabled flag.
+      tm.setGlobalIdsEnabled(globalIdsEnabled);
+      tm.setInterruptThreads(interruptThreads);
+      if (integrityFactory != null)
+         tm.setTransactionIntegrity(integrityFactory.createTransactionIntegrity());
+      else
+         tm.setTransactionIntegrity(null);
+      
+      // Bind reference to TM in JNDI
+      // Our TM also implement the tx importer and exporter
+      // interfaces, so we bind it under those names too.
+      // Other transaction managers may have seperate
+      // implementations of these objects, so they are
+      // accessed under separate names.
+      bindRef(JNDI_NAME, "org.jboss.tm.TxManager");
+      bindRef(JNDI_IMPORTER, "org.jboss.tm.TransactionPropagationContextImporter");
+      bindRef(JNDI_EXPORTER, "org.jboss.tm.TransactionPropagationContextFactory");
+   }
+
+   public void stop()
+   {
+      try
+      {
+         // Remove TM, importer and exporter from JNDI
+         Context ctx = initialContext;
+         ctx.unbind(JNDI_NAME);
+         ctx.unbind(JNDI_IMPORTER);
+         ctx.unbind(JNDI_EXPORTER);
+      }
+      catch (Exception e)
+      {
+         log.error("Failed to clear JNDI bindings", e);
+      }
+   }
+
+   /**
+    * Set the Recover logger
+    *
+    * @param recoveryLogger
+    */
+   public void setRecoveryLogger(RecoveryLogger recoveryLogger)
+   {
+      this.recoveryLogger = recoveryLogger;
+      // inject the logger into the TxManager
+      TxManager.getInstance().setRecoveryLogger(this.recoveryLogger);
+   }
+
+
+   /**
+    * Set the Transaciton integrity factory
+    *
+    * @param factory the transaction integrity factory
+    */
+   public void setTransactionIntegrityFactory(TransactionIntegrityFactory factory)
+   {
+      this.integrityFactory = factory;
+      if (tm != null)
+      {
+         if (factory != null)
+            tm.setTransactionIntegrity(factory.createTransactionIntegrity());
+         else
+            tm.setTransactionIntegrity(null);
+      }
+   }
+
+   /**
+    * Gets the "global ids enabled" flag.
+    *
+    * @return an <code>boolean</code> value
+    */
+   public boolean getGlobalIdsEnabled()
+   {
+      return globalIdsEnabled;
+   }
+
+   /**
+    * Sets the "global ids enabled" flag.
+    *
+    * @param newValue an <code>boolean</code> value
+    */
+   public void setGlobalIdsEnabled(boolean newValue)
+   {
+      globalIdsEnabled = newValue;
+      if (tm != null) // Update TM setting
+         tm.setGlobalIdsEnabled(newValue);
+   }
+
+   /**
+    * Is thread interruption enabled at transaction timeout
+    * 
+    * @return true for interrupt threads, false otherwise
+    */
+   public boolean isInterruptThreads() 
+   {
+      return interruptThreads;
+   }
+
+   /**
+    * Enable/disable thread interruption at transaction timeout.
+    * 
+    * @param interruptThreads pass true to interrupt threads, false otherwise
+    */
+   public void setInterruptThreads(boolean interruptThreads) 
+   {
+      this.interruptThreads = interruptThreads;
+      if (tm != null)
+         tm.setInterruptThreads(interruptThreads);
+   }
+
+   /**
+    * Describe <code>getTransactionTimeout</code> method here.
+    *
+    * @return an <code>int</code> value
+    */
+   public int getTransactionTimeout()
+   {
+      if (tm != null) // Get timeout value from TM (in case it was changed).
+         timeout = tm.getDefaultTransactionTimeout();
+      return timeout;
+   }
+
+   /**
+    * Describe <code>setTransactionTimeout</code> method here.
+    *
+    * @param timeout an <code>int</code> value
+    * @jmx:managed-attribute
+    */
+   public void setTransactionTimeout(int timeout)
+   {
+      this.timeout = timeout;
+      if (tm != null) // Update TM default timeout
+         tm.setDefaultTransactionTimeout(timeout);
+   }
+   
+   /**
+    * Sets the completion retry limit. This is the maximum number of times that
+    * the transaction manager retries a completion operation (either commit or
+    * rollback) on a resource (either a remote <code>Resource</code> or an 
+    * <code>XAResource</code>) that raised a transient exception. Whoever called 
+    * the transaction manager is blocked while the completion retries are 
+    * performed. If the completion retry limit is reached, the transaction
+    * manager abandons the retries and throws a heuristic hazard exception.  
+    * 
+    * @param maxCompletionRetries the completion retry limit.
+    */
+   public void setCompletionRetryLimit(int maxCompletionRetries)
+   {
+      this.completionRetryLimit = maxCompletionRetries;
+      if (tm != null) // Update TM setting
+         tm.setCompletionRetryLimit(maxCompletionRetries);
+   }
+   
+   /**
+    * Gets the completion retry limit. This is the maximum number of times that
+    * the transaction manager retries a completion operation (either commit or
+    * rollback) on a resource (either a remote <code>Resource</code> or an 
+    * <code>XAResource</code>) that raised a transient exception. Whoever called 
+    * the transaction manager is blocked while the completion retries are 
+    * performed. If the completion retry limit is reached, the transaction
+    * manager abandons the retries and throws a heuristic hazard exception.  
+    * 
+    * @return the completion retry limit.
+    */
+   public int getCompletionRetryLimit()
+   {
+      return completionRetryLimit;
+   }
+   
+   /**
+    * Sets the completion retry timeout. The completion retry timeout is the 
+    * number of seconds that the transaction manager waits before retrying a 
+    * completion operation (either commit or rollback) on a resource (either a
+    * remote <code>Resource</code> or an <code>XAResource</code>) that raised a 
+    * transient exception. This is a blocking timeout (whoever called the 
+    * transaction manager is blocked until the commit or rollback is retried)
+    * and is applicable if the transaction manager did not report a heuristic
+    * hazard for the transaction. If a heuristic hazard has been reported, then
+    * the applicable timouts are the non-blocking ones: the XA retry timeout and 
+    * the prepared timeout.    
+    * 
+    * @param seconds the timeout (in seconds) for retrying a completion 
+    *                operation after a transient exception and before the 
+    *                transaction manager reports a heuristic hazard. 
+    */
+   public void setCompletionRetryTimeout(int seconds)
+   {
+      this.completionRetryTimeout = seconds;
+      if (tm != null) // Update TM setting
+         tm.setCompletionRetryTimeout(seconds);
+   }
+   
+   /**
+    * Gets the completion retry timeout. The completion retry timeout is the 
+    * number of seconds that the transaction manager waits before retrying a 
+    * completion operation (either commit or rollback) on a resource (either a
+    * remote <code>Resource</code> or an <code>XAResource</code>) that raised a 
+    * transient exception. This is a blocking timeout (whoever called the 
+    * transaction manager is blocked until the commit or rollback is retried)
+    * and is applicable if the transaction manager did not report a heuristic
+    * hazard for the transaction. If a heuristic hazard has been reported, then
+    * the applicable timouts are the non-blocking ones: the XA retry timeout and 
+    * the prepared timeout.    
+    *
+    * @return the timeout (in seconds) for retrying a completion operation 
+    *         after a transient exception and before the transaction manager 
+    *         reports a heuristic hazard.
+    */
+   public int getCompletionRetryTimeout()
+   {
+      return completionRetryTimeout;
+   }
+   
+   /**
+    * Sets the XA retry timeout.
+    * 
+    * @param xaRetryTimeout the timeout (in seconds) for retrying operations on 
+    *                       XA resources.
+    */
+   public void setXARetryTimeout(int xaRetryTimeout)
+   {
+      this.xaRetryTimeout = xaRetryTimeout;
+      if (tm != null) // Update TM setting
+         tm.setXARetryTimeout(xaRetryTimeout);
+   }
+   
+   /**
+    * Gets the XA retry timeout.
+    * 
+    * @return the timeout (in seconds) for retrying operations on XA resources.
+    */
+   public int getXARetryTimeout()
+   {
+      return xaRetryTimeout;
+   }
+   
+   /**
+    * Sets the prepared timeout. A transaction branch that is the prepared 
+    * state waits for an amount of time equal to the prepared timeout before 
+    * generating a call to <code>replayCompletion</code> on its recovery 
+    * coordinator.
+    * 
+    * @param preparedTimeout the timeout (in seconds) for a transaction branch 
+    *                        in the prepared state.
+    */
+   public void setPreparedTimeout(int preparedTimeout)
+   {
+      this.preparedTimeout = preparedTimeout;
+      if (tm != null) // Update TM setting
+         tm.setPreparedTimeout(preparedTimeout);
+   }
+   
+   /**
+    * Gets the prepared timeout. A transaction branch that is the prepared 
+    * state waits for an amount of time equal to the prepared timeout before 
+    * generating a call to <code>replayCompletion</code> on its recovery 
+    * coordinator.
+    * 
+    * @return the timeout (in seconds) for a transaction branch in the prepared 
+    *         state.
+    */
+   public int getPreparedTimeout()
+   {
+      return preparedTimeout;
+   }
+   
+   /**
+    * Sets the boolean attribute "root branch remembers heuristic decisions".
+    * If this attribute is true, the root branch remembers a heuristically
+    * completed transaction until explicitly told (through a call to the MBean
+    * operation <code>forget</code>) to forget that transaction. If this 
+    * attribute is false, the root branch immediately forgets a transaction
+    * when the transaction completes.
+    * 
+    * @param newValue true if the root branch should remember heuristic 
+    *                 decisions, false otherwise.
+    */
+   public void setRootBranchRemembersHeuristicDecisions(boolean newValue)
+   {
+      this.rootBranchRemembersHeuristicDecisions = newValue;
+      if (tm != null) // Update TM setting
+         tm.setRootBranchRemembersHeuristicDecisions(newValue);
+   }
+   
+   /**
+    * Gets the boolean attribute "root branch remembers heuristic decisions".
+    * If this attribute is true, the root branch remembers a heuristically
+    * completed transaction until explicitly told (through a call to the MBean
+    * operation <code>forget</code>) to forget that transaction. If this 
+    * attribute is false, the root branch immediately forgets a transaction
+    * when the transaction completes.
+    * 
+    * @return true if the root branch remember heuristic decisions, 
+    *         false otherwise.
+    */
+   public boolean isRootBranchRemembersHeuristicDecisions()
+   {
+      return rootBranchRemembersHeuristicDecisions;
+   }
+   
+   /**
+    * Sets the boolean attribute "report heuristic hazard as heuristic mixed".
+    * If this attribute is true, each of the commit methods of the JTA API 
+    * (<code>javax.transaction.Transaction.commit()</code>, 
+    * <code>javax.transaction.TransactionManager.commit()</code>, and
+    * <code>javax.transaction.UserTransaction.commit()</code>) throws a
+    * <code>HeuristicMixedException</code> to report a heuristic hazard to its 
+    * caller. If the attribute is false, those methods do not report heuristic
+    * hazards to their callers. In any case, transactions with heuristic hazard
+    * status are listed by the MBean operation 
+    * <code>listHeuristicallyCompletedTransactions()</code>.
+    * 
+    * @param newValue true if a JTA commit should throw 
+    *         <code>HeuristicMixedException</code> to report a heuristic hazard
+    *         to its caller, or false if a JTA commit should not report a 
+    *         heuristic hazard to its caller.
+    */
+   public void setReportHeuristicHazardAsHeuristicMixed(boolean newValue)
+   {
+      this.reportHeuristicHazardAsHeuristicMixed = newValue;
+      if (tm != null) // Update TM setting
+         tm.setReportHeuristicHazardAsHeuristicMixed(newValue);
+      
+   }
+   
+   /**
+    * Gets the boolean attribute "report heuristic hazard as heuristic mixed".
+    * If this attribute is true, each of the commit methods of the JTA API 
+    * (<code>javax.transaction.Transaction.commit()</code>, 
+    * <code>javax.transaction.TransactionManager.commit()</code>, and
+    * <code>javax.transaction.UserTransaction.commit()</code>) throws a
+    * <code>HeuristicMixedException</code> to report a heuristic hazard to its 
+    * caller. If the attribute is false, those methods do not report heuristic
+    * hazards to their callers. In any case, transactions with heuristic hazard
+    * status are listed by the MBean operation 
+    * <code>listHeuristicallyCompletedTransactions()</code>.
+    *  
+    * @return true if a JTA commit throws <code>HeuristicMixedException</code>
+    *         to report a heuristic hazard to its caller, or false if a JTA
+    *         commit does not report a heuristic hazard to its caller. 
+    */
+   boolean isReportHeuristicHazardAsHeuristicMixed()
+   {
+      return reportHeuristicHazardAsHeuristicMixed;
+   }
+   
+  /**
+    * mbean get-set pair for field xidFactory
+    * Get the value of xidFactory
+    * @return value of xidFactory
+    *
+    */
+   public XidFactoryBase getXidFactory()
+   {
+      return xidFactory;
+   }
+
+   /**
+    * Set the value of xidFactory
+    * @param xidFactory  Value to assign to xidFactory
+    *
+    */
+   public void setXidFactory(XidFactoryBase xidFactory)
+   {
+      this.xidFactory = xidFactory;
+   }
+
+
+   /**
+    * mbean get-set pair for field transactionManager
+    * Get the value of transactionManager
+    * @return value of transactionManager
+    *
+    */
+   public TransactionManager getTransactionManager()
+   {
+      return tm;
+   }
+
+   /**
+    * Get the xa terminator
+    * 
+    * @return the xa terminator
+    */
+   public JBossXATerminator getXATerminator()
+   {
+      return tm;
+   }
+
+   /**
+    * Counts the number of transactions
+    *
+    * @return the number of active transactions
+    *
+    */
+   public long getTransactionCount()
+   {
+      return tm.getTransactionCount();
+   }
+   /** The number of commits.
+    *
+    * @return the number of transactions that have been committed
+    *
+    */
+   public long getCommitCount()
+   {
+      return tm.getCommitCount();
+   }
+   /** The number of rollbacks.
+    *
+    * @return the number of transactions that have been rolled back
+    *
+    */
+   public long getRollbackCount()
+   {
+      return tm.getRollbackCount();
+   }
+   
+   /**
+    * Lists the in-doubt transactions. 
+    * 
+    * @return a string with a text listing of in-doubt transactions. 
+    */
+   public String listInDoubtTransactions()
+   {
+      return tm.listInDoubtTransactions();
+   }
+   
+   /**
+    * Heuristically commits an in-doubt transaction.
+    * 
+    * @param localTransactionId the local id of the in-doubt transaction to be
+    *                           heuristically committed.
+    */
+   public void heuristicallyCommit(long localTransactionId)
+   {
+      tm.heuristicallyCommit(localTransactionId);
+   }
+   
+   /**
+    * Heuristically commits all in-doubt transactions.
+    */
+  public  void heuristicallyCommitAll()
+  {
+     tm.heuristicallyCommitAll();
+  }
+   
+   /**
+    * Heuristically rolls back an in-doubt transaction.
+    * 
+    * @param localTransactionId the local id of the in-doubt transaction to be
+    *                           heuristically rolled back.
+    */
+   public void heuristicallyRollback(long localTransactionId)
+   {
+      tm.heuristicallyRollback(localTransactionId);
+   }
+   
+   /**
+    * Heuristically rolls back all in-doubt transactions.
+    */
+   public void heuristicallyRollbackAll()
+   {
+      tm.heuristicallyRollbackAll();
+   }
+   
+   /**
+    * Lists the heuristically completed transactions coordinated by this
+    * transaction manager. A transaction that was heuristically completed
+    * by a call to <code>heuristicallyCommit(long localTransactionId)</code>,
+    * <code>heuristicallyCommitAll()</code>,
+    * <code>heuristicallyRollback(long localTransactionId)</code>, or
+    * <code>heuristicallyRollbackAll()</code> does not appear in the listing, 
+    * as that transaction had a foreign coordinator. 
+    * 
+    * @return a string with a text listing of heuristically completed
+    *         transactions.
+    */
+   public String listHeuristicallyCompletedTransactions()
+   {
+      return tm.listHeuristicallyCompletedTransactions();
+   }
+   
+   /**
+    * Forgets a heuristically completed transaction coordinated by this 
+    * transaction manager.
+    * 
+    * @param localTransactionId the local id of a heuristically completed 
+    *                           transaction coordinated by this transaction 
+    *                           manager.
+    */
+   public void forget(long localTransactionId)
+   {
+      tm.forget(localTransactionId);
+   }
+   
+   /**
+    * Forgets all heuristically completed transactions coordinated by this 
+    * transaction manager.
+    */
+   public void forgetAll()
+   {
+      tm.forgetAll();
+   }
+
+   /**
+    * The <code>registerXAExceptionFormatter</code> method
+    *
+    * @param clazz a <code>Class</code> value
+    * @param formatter a <code>XAExceptionFormatter</code> value
+    *
+    */
+   public void registerXAExceptionFormatter(Class clazz, XAExceptionFormatter formatter)
+   {
+      xaExceptionFormatters.put(clazz, formatter);
+   }
+
+   /**
+    * The <code>unregisterXAExceptionFormatter</code> method
+    *
+    * @param clazz a <code>Class</code> value
+    *
+    */
+   public void unregisterXAExceptionFormatter(Class clazz)
+   {
+      xaExceptionFormatters.remove(clazz);
+   }
+
+   public void formatXAException(XAException xae, Logger log)
+   {
+      Class clazz = xae.getClass();
+      while (clazz != XAException.class)
+      {
+         XAExceptionFormatter formatter = (XAExceptionFormatter) xaExceptionFormatters.get(clazz);
+         if (formatter != null)
+         {
+            formatter.formatXAException(xae, log);
+            return;
+         } // end of if ()
+         clazz = clazz.getSuperclass();
+      }
+   }
+
+   // ObjectFactory implementation ----------------------------------
+
+   public Object getObjectInstance(Object obj, Name name,
+         Context nameCtx, Hashtable environment)
+         throws Exception
+   {
+      // Return the transaction manager
+      return tm;
+   }
+
+
+   // Private -------------------------------------------------------
+
+   private void bindRef(String jndiName, String className)
+         throws Exception
+   {
+      Reference ref = new Reference(className, getClass().getName(), null);
+      initialContext.bind(jndiName, ref);
+   }
+}

Added: trunk/transaction/src/main/org/jboss/tm/TransactionManagerService.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/TransactionManagerService.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/TransactionManagerService.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,284 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+import javax.management.ObjectName;
+import javax.transaction.TransactionManager;
+
+import org.jboss.system.ServiceMBeanSupport;
+import org.jboss.tm.integrity.TransactionIntegrityFactory;
+import org.jboss.tm.recovery.RecoveryLogger;
+import org.jboss.tm.recovery.RecoveryLoggerInstance;
+
+/**
+ *  This is a JMX service which manages the TransactionManager.
+ *  The service creates it and binds a Reference to it into JNDI.
+ *
+ *  @see TxManager
+ *  @author <a href="mailto:rickard.oberg at telkel.com">Rickard Oberg</a>
+ *  @author <a href="mailto:osh at sparre.dk">Ole Husgaard</a>
+ *  @author <a href="mailto:toby.allsopp at peace.com">Toby Allsopp</a>
+ *  @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ *  @version $Revision: 37459 $
+ *
+ * @jmx.mbean extends="org.jboss.system.ServiceMBean"
+ */
+public class TransactionManagerService
+      extends ServiceMBeanSupport
+      implements TransactionManagerServiceMBean
+{
+   private ObjectName xidFactory;
+
+   private TransactionManagerInitializer initializer = new TransactionManagerInitializer();
+   
+   // Constants -----------------------------------------------------
+   public static String JNDI_NAME = "java:/TransactionManager";
+   public static String JNDI_IMPORTER = "java:/TransactionPropagationContextImporter";
+   public static String JNDI_EXPORTER = "java:/TransactionPropagationContextExporter";
+
+   protected void startService()
+         throws Exception
+   {
+      XidFactoryMBean xidFactoryObj = (XidFactoryMBean) getServer().getAttribute(xidFactory, "Instance");
+      initializer.setXidFactory(xidFactoryObj);
+      initializer.start();
+   }
+
+   protected void stopService()
+   {
+      initializer.stop();
+   }
+
+   /**
+    * Set the Recover logger
+    *
+    * @param recoveryLogger
+    * @jmx:managed-attribute
+    */
+   public void setRecoveryLogger(RecoveryLoggerInstance recoveryLogger)
+   {
+      initializer.setRecoveryLogger(recoveryLogger.getInstance());
+   }
+
+   /**
+    * Set the Transaction integrity factory
+    *
+    * @param factory the factory 
+    */
+   public void setTransactionIntegrityFactory(TransactionIntegrityFactory factory)
+   {
+      initializer.setTransactionIntegrityFactory(factory);
+   }
+
+   /**
+    * mbean get-set pair for field xidFactory
+    * Get the value of xidFactory
+    * @return value of xidFactory
+    *
+    * @jmx:managed-attribute
+    */
+   public ObjectName getXidFactory()
+   {
+      return xidFactory;
+   }
+
+   /**
+    * Set the value of xidFactory
+    * @param xidFactory  Value to assign to xidFactory
+    *
+    * @jmx:managed-attribute
+    */
+   public void setXidFactory(ObjectName xidFactory)
+   {
+      this.xidFactory = xidFactory;
+   }
+
+   public void setRecoveryLogger(RecoveryLogger recoveryLogger)
+   {
+      initializer.setRecoveryLogger(recoveryLogger);
+   }
+
+   public boolean getGlobalIdsEnabled()
+   {
+      return initializer.getGlobalIdsEnabled();
+   }
+
+   public void setGlobalIdsEnabled(boolean newValue)
+   {
+      initializer.setGlobalIdsEnabled(newValue);
+   }
+
+   public boolean isInterruptThreads()
+   {
+      return initializer.isInterruptThreads();
+   }
+
+   public void setInterruptThreads(boolean interruptThreads)
+   {
+      initializer.setInterruptThreads(interruptThreads);
+   }
+
+   public int getTransactionTimeout()
+   {
+      return initializer.getTransactionTimeout();
+   }
+
+   public void setTransactionTimeout(int timeout)
+   {
+      initializer.setTransactionTimeout(timeout);
+   }
+
+   public int getCompletionRetryLimit()
+   {
+      return initializer.getCompletionRetryLimit();
+   }
+
+   public void setCompletionRetryLimit(int limit)
+   {
+      initializer.setCompletionRetryLimit(limit);
+   }
+   
+   public int getCompletionRetryTimeout()
+   {
+      return initializer.getCompletionRetryTimeout();
+   }
+
+   public void setCompletionRetryTimeout(int timeout)
+   {
+      initializer.setCompletionRetryTimeout(timeout);
+   }
+   
+   public int getXARetryTimeout()
+   {
+      return initializer.getXARetryTimeout();
+   }
+
+   public void setXARetryTimeout(int timeout)
+   {
+      initializer.setXARetryTimeout(timeout);
+   }
+
+   public int getPreparedTimeout()
+   {
+      return initializer.getPreparedTimeout();
+   }
+
+   public void setPreparedTimeout(int timeout)
+   {
+      initializer.setPreparedTimeout(timeout);
+   }
+   
+   public boolean isRootBranchRemembersHeuristicDecisions()
+   {
+      return initializer.isRootBranchRemembersHeuristicDecisions();
+   }
+
+   public void setRootBranchRemembersHeuristicDecisions(boolean newValue)
+   {
+      initializer.setRootBranchRemembersHeuristicDecisions(newValue);
+   }
+
+   public boolean isReportHeuristicHazardAsHeuristicMixed()
+   {
+      return initializer.isReportHeuristicHazardAsHeuristicMixed();
+   }
+
+   public void setReportHeuristicHazardAsHeuristicMixed(boolean newValue)
+   {
+      initializer.setReportHeuristicHazardAsHeuristicMixed(newValue);
+   }
+
+   public TransactionManager getTransactionManager()
+   {
+      return initializer.getTransactionManager();
+   }
+
+   public JBossXATerminator getXATerminator()
+   {
+      return initializer.getXATerminator();
+   }
+
+   public long getTransactionCount()
+   {
+      return initializer.getTransactionCount();
+   }
+
+   public long getCommitCount()
+   {
+      return initializer.getCommitCount();
+   }
+
+   public long getRollbackCount()
+   {
+      return initializer.getRollbackCount();
+   }
+
+   public String listInDoubtTransactions()
+   {
+      return initializer.listInDoubtTransactions();
+   }
+   
+   public void heuristicallyCommit(long localTransactionId)
+   {
+      initializer.heuristicallyCommit(localTransactionId);
+   }
+   
+   public void heuristicallyCommitAll()
+   {
+      initializer.heuristicallyCommitAll();
+   }
+   
+   public void heuristicallyRollback(long localTransactionId)
+   {
+      initializer.heuristicallyRollback(localTransactionId);
+   }
+   
+   public void heuristicallyRollbackAll()
+   {
+      initializer.heuristicallyRollbackAll();
+   }
+   
+   public String listHeuristicallyCompletedTransactions()
+   {
+      return initializer.listHeuristicallyCompletedTransactions();
+   }
+   
+   public void forget(long localTransactionId)
+   {
+      initializer.forget(localTransactionId);
+   }
+   
+   public void forgetAll()
+   {
+      initializer.forgetAll();
+   }
+   
+   public void registerXAExceptionFormatter(Class clazz, XAExceptionFormatter formatter)
+   {
+      initializer.registerXAExceptionFormatter(clazz, formatter);
+   }
+
+   public void unregisterXAExceptionFormatter(Class clazz)
+   {
+      initializer.unregisterXAExceptionFormatter(clazz);
+   }
+}

Added: trunk/transaction/src/main/org/jboss/tm/TransactionManagerServiceMBean.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/TransactionManagerServiceMBean.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/TransactionManagerServiceMBean.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,375 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2005, JBoss Inc., and individual contributors as indicated
+ * by the @authors tag. See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+ */
+package org.jboss.tm;
+
+import javax.management.ObjectName;
+
+import org.jboss.mx.util.ObjectNameFactory;
+import org.jboss.system.ServiceMBean;
+import org.jboss.tm.integrity.TransactionIntegrityFactory;
+import org.jboss.tm.recovery.RecoveryLoggerInstance;
+
+/**
+ * TransactionManagerService MBean interface.
+ *
+ * @see TxManager
+ * @version $Revision: 44337 $
+ */
+public interface TransactionManagerServiceMBean extends ServiceMBean, TransactionManagerFactory
+{
+   ObjectName OBJECT_NAME = ObjectNameFactory.create("jboss:service=TransactionManager");
+
+   /**
+    * Set the Recover logger
+    * @param recoveryLogger
+    */
+   void setRecoveryLogger(RecoveryLoggerInstance recoveryLogger);
+   
+   /**
+    * Set the Integrity checker factory 
+    * 
+    * @param factory the integrity checker factory
+    */
+   void setTransactionIntegrityFactory(TransactionIntegrityFactory factory);
+
+   /**
+    * Describe <code>getGlobalIdsEnabled</code> method here.
+    * @return an <code>boolean</code> value
+    */
+   boolean getGlobalIdsEnabled();
+
+   /**
+    * Describe <code>setGlobalIdsEnabled</code> method here.
+    * @param newValue an <code>boolean</code> value
+    */
+   void setGlobalIdsEnabled(boolean newValue);
+
+   /**
+    * Is thread interruption enabled at transaction timeout
+    * @return true for interrupt threads, false otherwise
+    */
+   boolean isInterruptThreads();
+
+   /**
+    * Enable/disable thread interruption at transaction timeout.
+    * @param interruptThreads pass true to interrupt threads, false otherwise
+    */
+   void setInterruptThreads(boolean interruptThreads);
+
+   /**
+    * Describe <code>getTransactionTimeout</code> method here.
+    * @return an <code>int</code> value
+    */
+   int getTransactionTimeout();
+
+   /**
+    * Describe <code>setTransactionTimeout</code> method here.
+    * @param timeout an <code>int</code> value
+    */
+   void setTransactionTimeout(int timeout);
+
+   /**
+    * Gets the completion retry limit. This is the maximum number of times that
+    * the transaction manager retries a completion operation (either commit or
+    * rollback) on a resource (either a remote <code>Resource</code> or an 
+    * <code>XAResource</code>) that raised a transient exception. Whoever called 
+    * the transaction manager is blocked while the completion retries are 
+    * performed. If the completion retry limit is reached, the transaction
+    * manager abandons the retries and throws a heuristic hazard exception.  
+    * 
+    * @return the completion retry limit.
+    */
+   int getCompletionRetryLimit();
+
+   /**
+    * Sets the completion retry limit. This is the maximum number of times that
+    * the transaction manager retries a completion operation (either commit or
+    * rollback) on a resource (either a remote <code>Resource</code> or an 
+    * <code>XAResource</code>) that raised a transient exception. Whoever called 
+    * the transaction manager is blocked while the completion retries are 
+    * performed. If the completion retry limit is reached, the transaction
+    * manager abandons the retries and throws a heuristic hazard exception.  
+    * 
+    * @param maxCompletionRetries the completion retry limit.
+    */
+   void setCompletionRetryLimit(int maxCompletionRetries);
+   
+   /**
+    * Gets the completion retry timeout. The completion retry timeout is the 
+    * number of seconds that the transaction manager waits before retrying a 
+    * completion operation (either commit or rollback) on a resource (either a
+    * remote <code>Resource</code> or an <code>XAResource</code>) that raised a 
+    * transient exception. This is a blocking timeout (whoever called the 
+    * transaction manager is blocked until the commit or rollback is retried)
+    * and is applicable if the transaction manager did not report a heuristic
+    * hazard for the transaction. If a heuristic hazard has been reported, then
+    * the applicable timouts are the non-blocking ones: the XA retry timeout and 
+    * the prepared timeout.    
+    *
+    * @return the timeout (in seconds) for retrying a completion operation 
+    *         after a transient exception and before the transaction manager 
+    *         reports a heuristic hazard.
+    */
+   int getCompletionRetryTimeout();
+   
+   /**
+    * Sets the completion retry timeout. The completion retry timeout is the 
+    * number of seconds that the transaction manager waits before retrying a 
+    * completion operation (either commit or rollback) on a resource (either a
+    * remote <code>Resource</code> or an <code>XAResource</code>) that raised a 
+    * transient exception. This is a blocking timeout (whoever called the 
+    * transaction manager is blocked until the commit or rollback is retried)
+    * and is applicable if the transaction manager did not report a heuristic
+    * hazard for the transaction. If a heuristic hazard has been reported, then
+    * the applicable timouts are the non-blocking ones: the XA retry timeout and 
+    * the prepared timeout.    
+    * 
+    * @param seconds the timeout (in seconds) for retrying a completion 
+    *                operation after a transient exception and before the 
+    *                transaction manager reports a heuristic hazard. 
+    */
+   void setCompletionRetryTimeout(int seconds);
+   
+   /**
+    * Gets the XA retry timeout. After reaching the completion retry limit and
+    * reporting a heuristic hazard to its caller, the transaction manager will 
+    * still attempt to commit or rollback an XA resource that raised a
+    * transient exception. This is the time interval (in seconds) between XA 
+    * completion retries that is applicable if a heuristic hazard has been
+    * reported for a transaction.
+    *  
+    * @return the timeout (in seconds) for retrying commit or rollback 
+    *         operations on XA resources. 
+    */
+   int getXARetryTimeout();
+   
+   /**
+    * Sets the XA retry timeout. After reaching the completion retry limit and
+    * reporting a heuristic hazard to its caller, the transaction manager will 
+    * still attempt to commit or rollback an XA resource that raised a
+    * transient exception. This is the time interval (in seconds) between XA 
+    * completion retries that is applicable if a heuristic hazard has been
+    * reported for a transaction.
+    * 
+    * @param seconds the timeout (in seconds) for retrying commit or rollback
+    *                operations on XA resources.
+    */
+   void setXARetryTimeout(int seconds);
+   
+   /**
+    * Gets the prepared timeout. A transaction branch that is the prepared 
+    * state waits for an amount of time equal to the prepared timeout before 
+    * generating a call to <code>replayCompletion</code> on its recovery 
+    * coordinator.
+    * 
+    * @return the timeout (in seconds) for a transaction branch in the prepared 
+    *         state.
+    */
+   int getPreparedTimeout();
+   
+   /**
+    * Sets the prepared timeout. A transaction branch that is the prepared 
+    * state waits for an amount of time equal to the prepared timeout before 
+    * generating a call to <code>replayCompletion</code> on its recovery 
+    * coordinator.
+    * 
+    * @param seconds the timeout (in seconds) for a transaction branch in the 
+    *                    prepared state.
+    */
+   void setPreparedTimeout(int seconds);
+   
+   /**
+    * Gets the boolean attribute "root branch remembers heuristic decisions".
+    * If this attribute is true, the root branch remembers a heuristically
+    * completed transaction until explicitly told (through a call to the MBean
+    * operation <code>forget</code>) to forget that transaction. If this 
+    * attribute is false, the root branch immediately forgets a transaction
+    * when the transaction completes.
+    * 
+    * @return true if the root branch remember heuristic decisions, 
+    *         false otherwise.
+    */
+   boolean isRootBranchRemembersHeuristicDecisions();
+   
+   /**
+    * Sets the boolean attribute "root branch remembers heuristic decisions".
+    * If this attribute is true, the root branch remembers a heuristically
+    * completed transaction until explicitly told (through a call to the MBean
+    * operation <code>forget</code>) to forget that transaction. If this 
+    * attribute is false, the root branch immediately forgets a transaction
+    * when the transaction completes.
+    * 
+    * @param newValue true if the root branch should remember heuristic 
+    *                 decisions, false otherwise.
+    */
+   void setRootBranchRemembersHeuristicDecisions(boolean newValue);
+   
+   /**
+    * Gets the boolean attribute "report heuristic hazard as heuristic mixed".
+    * If this attribute is true, each of the commit methods of the JTA API 
+    * (<code>javax.transaction.Transaction.commit()</code>, 
+    * <code>javax.transaction.TransactionManager.commit()</code>, and
+    * <code>javax.transaction.UserTransaction.commit()</code>) throws a
+    * <code>HeuristicMixedException</code> to report a heuristic hazard to its 
+    * caller. If the attribute is false, those methods do not report heuristic
+    * hazards to their callers. In any case, transactions with heuristic hazard
+    * status are listed by the MBean operation 
+    * <code>listHeuristicallyCompletedTransactions()</code>.
+    *  
+    * @return true if a JTA commit throws <code>HeuristicMixedException</code>
+    *         to report a heuristic hazard to its caller, or false if a JTA
+    *         commit does not report a heuristic hazard to its caller. 
+    */
+   boolean isReportHeuristicHazardAsHeuristicMixed();
+   
+   /**
+    * Sets the boolean attribute "report heuristic hazard as heuristic mixed".
+    * If this attribute is true, each of the commit methods of the JTA API 
+    * (<code>javax.transaction.Transaction.commit()</code>, 
+    * <code>javax.transaction.TransactionManager.commit()</code>, and
+    * <code>javax.transaction.UserTransaction.commit()</code>) throws a
+    * <code>HeuristicMixedException</code> to report a heuristic hazard to its 
+    * caller. If the attribute is false, those methods do not report heuristic
+    * hazards to their callers. In any case, transactions with heuristic hazard
+    * status are listed by the MBean operation 
+    * <code>listHeuristicallyCompletedTransactions()</code>.
+    * 
+    * @param newValue true if a JTA commit should throw 
+    *         <code>HeuristicMixedException</code> to report a heuristic hazard
+    *         to its caller, or false if a JTA commit should not report a 
+    *         heuristic hazard to its caller.
+    */
+   void setReportHeuristicHazardAsHeuristicMixed(boolean newValue);
+   
+   /**
+    * mbean get-set pair for field xidFactory Get the value of xidFactory
+    * @return value of xidFactory
+    */
+   ObjectName getXidFactory();
+
+   /**
+    * Set the value of xidFactory
+    * @param xidFactory Value to assign to xidFactory
+    */
+   void setXidFactory(ObjectName xidFactory);
+
+   /**
+    * Get the xa terminator
+    * @return the xa terminator    
+    */
+   JBossXATerminator getXATerminator();
+
+   /**
+    * Counts the number of transactions
+    * @return the number of active transactions
+    */
+   long getTransactionCount();
+
+   /**
+    * The number of commits.
+    * @return the number of transactions that have been committed
+    */
+   long getCommitCount();
+
+   /**
+    * The number of rollbacks.
+    * @return the number of transactions that have been rolled back
+    */
+   long getRollbackCount();
+
+   /**
+    * Lists the in-doubt transactions. 
+    * 
+    * @return a string with a text listing of in-doubt transactions. 
+    */
+   String listInDoubtTransactions();
+   
+   /**
+    * Heuristically commits an in-doubt transaction.
+    * 
+    * @param localTransactionId the local id of the in-doubt transaction to be
+    *                           heuristically committed.
+    */
+   void heuristicallyCommit(long localTransactionId);
+   
+   /**
+    * Heuristically commits all in-doubt transactions.
+    */
+   void heuristicallyCommitAll();
+   
+   /**
+    * Heuristically rolls back an in-doubt transaction.
+    * 
+    * @param localTransactionId the local id of the in-doubt transaction to be
+    *                           heuristically rolled back.
+    */
+   void heuristicallyRollback(long localTransactionId);
+   
+   /**
+    * Heuristically rolls back all in-doubt transactions.
+    */
+   void heuristicallyRollbackAll();
+   
+   /**
+    * Lists the heuristically completed transactions coordinated by this
+    * transaction manager. A transaction that was heuristically completed
+    * by a call to <code>heuristicallyCommit(long localTransactionId)</code>,
+    * <code>heuristicallyCommitAll()</code>,
+    * <code>heuristicallyRollback(long localTransactionId)</code>, or
+    * <code>heuristicallyRollbackAll()</code> does not appear in the listing, 
+    * as that transaction had a foreign coordinator. 
+    * 
+    * @return a string with a text listing of heuristically completed
+    *         transactions.
+    */
+   String listHeuristicallyCompletedTransactions();
+   
+   /**
+    * Forgets a heuristically completed transaction coordinated by this 
+    * transaction manager.
+    * 
+    * @param localTransactionId the local id of a heuristically completed 
+    *                           transaction coordinated by this transaction 
+    *                           manager.
+    */
+   void forget(long localTransactionId);
+   
+   /**
+    * Forgets all heuristically completed transactions coordinated by this 
+    * transaction manager.
+    */
+   void forgetAll();
+   
+   /**
+    * The <code>registerXAExceptionFormatter</code> method
+    * @param clazz a <code>Class</code> value
+    * @param formatter a <code>XAExceptionFormatter</code> value
+    */
+   void registerXAExceptionFormatter(Class clazz, XAExceptionFormatter formatter);
+
+   /**
+    * The <code>unregisterXAExceptionFormatter</code> method
+    * @param clazz a <code>Class</code> value
+    */
+   void unregisterXAExceptionFormatter(Class clazz);
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/TxManager.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/TxManager.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/TxManager.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,1833 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.resource.spi.work.Work;
+import javax.resource.spi.work.WorkCompletedException;
+import javax.resource.spi.work.WorkException;
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.InvalidTransactionException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.Status;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.Xid;
+
+import org.jboss.logging.Logger;
+import org.jboss.tm.integrity.TransactionIntegrity;
+import org.jboss.tm.recovery.LogRecord;
+import org.jboss.tm.recovery.RecoveryLogger;
+import org.jboss.tm.recovery.TxCompletionHandler;
+import org.jboss.tm.remoting.interfaces.Coordinator;
+import org.jboss.tm.remoting.interfaces.RecoveryCoordinator;
+import org.jboss.tm.remoting.interfaces.Resource;
+import org.jboss.tm.remoting.interfaces.TxPropagationContext;
+import org.jboss.util.UnexpectedThrowable;
+import org.jboss.util.UnreachableStatementException;
+
+/**
+ * Our TransactionManager implementation.
+ *
+ * @author <a href="mailto:rickard.oberg at telkel.com">Rickard Oberg</a>
+ * @author <a href="mailto:marc.fleury at telkel.com">Marc Fleury</a>
+ * @author <a href="mailto:osh at sparre.dk">Ole Husgaard</a>
+ * @author <a href="mailto:jason at planet57.com">Jason Dillon</a>
+ * @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @author <a href="adrian at jboss.com">Adrian Brock</a>
+ * @author <a href="dimitris at jboss.org">Dimitris Andreadis</a>
+ * @version $Revision: 43424 $
+ * @deprecated Do not reference directly, use org.jboss.tm.TransactionManagerLocator
+ */
+public class TxManager
+      implements TransactionManager,
+                 TransactionPropagationContextImporter,
+                 TransactionPropagationContextFactory,
+                 TransactionLocalDelegate,
+                 TransactionTimeoutConfiguration,
+                 JBossXATerminator,
+                 StringRemoteRefConverter
+{
+   // Constants -----------------------------------------------------
+
+   // Attributes ----------------------------------------------------
+
+   /**
+    * True if the TxManager should keep a map from GlobalIds to transactions.
+    */
+   private boolean globalIdsEnabled = false;
+
+   /**
+    * True if distributed transaction management is enabled.
+    */
+   private boolean dtmEnabled = false;
+
+   /**
+    * Whether to interrupt threads at transaction timeout
+    */
+   private boolean interruptThreads = false;
+
+   /**
+    * Instance logger.
+    */
+   private Logger log = Logger.getLogger(this.getClass());
+
+   /**
+    * True if trace messages should be logged.
+    */
+   private boolean trace = log.isTraceEnabled();
+
+   /**
+    * Default timeout in milliseconds.
+    * Must be >= 1000!
+    */
+   private long timeOut = 5 * 60 * 1000;
+
+   // The following two fields are ints (not longs) because
+   // volatile 64Bit types are broken (i.e. access is not atomic) in most VMs, and we
+   // don't want to lock just for a statistic. Additionaly,
+   // it will take several years on a highly loaded system to
+   // exceed the int range. Note that we might loose an
+   // increment every now and then, since the ++ operation is
+   // not atomic on volatile data types.
+   /**
+    * A count of the transactions that have been committed
+    */
+   private volatile int commitCount;
+   /**
+    * A count of the transactions that have been rolled back
+    */
+   private volatile int rollbackCount;
+
+   /** The recovery logger */
+   private RecoveryLogger recoveryLogger;
+
+   /** Indicates that the recovery process was not yet performed */
+   private boolean recoveryPending = true;
+
+   /** The transaction integrity policy */
+   private TransactionIntegrity integrity;
+   
+   /** Number of completion retries before reporting a heuristic hazard. */
+   private int completionRetryLimit = 0; // default: no retries
+   
+   /** Blocking timeout between completion retries. */
+   private int completionRetryTimeout = 1 * 1000; // default: one sec
+   
+   /** Timeout (in milliseconds) for retrying operations on XA resources */
+   private int xaRetryTimeout = 60 * 1000;
+   
+   /** Timeout (in milliseconds) for a tx branch in the prepared state */
+   private int preparedTimeout = 60 * 1000;
+
+   /** True if the root branch should remember heuristic decisions */
+   private boolean rootBranchRemembersHeuristicDecisions = true;
+   
+   /** True if heuristic hazards generate JTA heuristic mixed exceptions */  
+   private boolean reportHeuristicHazardAsHeuristicMixed = false;
+
+   // Static --------------------------------------------------------
+
+   /**
+    * The singleton instance.
+    */
+   private static TxManager singleton = new TxManager();
+
+   /**
+    * Get a reference to the singleton instance.
+    */
+   public static TxManager getInstance()
+   {
+      return singleton;
+   }
+
+   // Constructors --------------------------------------------------
+
+   /**
+    * Private constructor for singleton. Use getInstance() to obtain
+    * a reference to the singleton.
+    */
+   private TxManager()
+   {
+      //make sure TxCapsule can be used
+      TransactionImpl.defaultXidFactory();
+   }
+
+   // Public --------------------------------------------------------
+
+   /**
+    * Clears all transactions. <p/>
+    * This method exists for testing purposes only. It should not be
+    * called during normal operation.
+    */
+   public void clear()
+   {
+      Collection txCollection = localIdTx.values();
+      synchronized (localIdTx)
+      {
+         Iterator i = txCollection.iterator();
+         while (i.hasNext())
+         {
+            TransactionImpl tx = (TransactionImpl) i.next();
+            tx.deactivate();
+         }
+      }
+      localIdTx.clear();
+      if (globalIdsEnabled)
+         globalIdTx.clear();
+   }
+   
+   /**
+    * Setter for attribute <code>globalIdsEnabled</code>.
+    */
+   public void setGlobalIdsEnabled(boolean newValue)
+   {
+      trace = log.isTraceEnabled(); // hack!
+      XidImpl.setTrulyGlobalIdsEnabled(newValue);
+      globalIdsEnabled = newValue;
+   }
+
+   /**
+    * Getter for attribute <code>globalIdsEnabled</code>.
+    */
+   public boolean getGlobalIdsEnabled()
+   {
+      return globalIdsEnabled;
+   }
+
+   /**
+    * Enable/disable thread interruption at transaction timeout.
+    *
+    * @param interruptThreads pass true to interrupt threads, false otherwise
+    */
+   public void setInterruptThreads(boolean interruptThreads)
+   {
+      this.interruptThreads = interruptThreads;
+   }
+
+   /**
+    * Is thread interruption enabled at transaction timeout
+    *
+    * @return true for interrupt threads, false otherwise
+    */
+   public boolean isInterruptThreads()
+   {
+      return interruptThreads;
+   }
+
+   /**
+    * Set the recovery logger
+    * 
+    * @param recoveryLogger the recovery logger
+    */
+   public void setRecoveryLogger(RecoveryLogger recoveryLogger)
+   {
+      this.recoveryLogger = recoveryLogger;
+   }
+
+   /**
+    * Get the recovery logger
+    * 
+    * @return the recovery logger
+    */
+   public RecoveryLogger getRecoveryLogger()
+   {
+      return recoveryLogger;
+   }
+
+   /**
+    * Clears the recovery pending flag. Called by the recovery manager, at
+    * the end of the recovery proccess. 
+    */
+   public void clearRecoveryPending()
+   {
+      this.recoveryPending = false;
+   }
+   
+   /**
+    * Returns the value of the recovery pending flag.
+    *  
+    * @return true is recovery is pending, false otherwise.
+    */
+   public boolean isRecoveryPending()
+   {
+      return recoveryPending;
+   }
+   
+   /**
+    * Set the transaction integrity policy
+    * 
+    * @param integrity the transaction integrity policy
+    */
+   public void setTransactionIntegrity(TransactionIntegrity integrity)
+   {
+      this.integrity = integrity;
+   }
+
+   /**
+    * Get the transaction integrity policy
+    * 
+    * @return the transaction integrity policy
+    */
+   public TransactionIntegrity getTransactionIntegrity()
+   {
+      return integrity;
+   }
+   
+   /**
+    * Sets the completion retry limit. This is the maximum number of times that
+    * the transaction manager retries a completion operation (either commit or
+    * rollback) on a resource (either a remote <code>Resource</code> or an 
+    * <code>XAResource</code>) that raised a transient exception. Whoever called 
+    * the transaction manager is blocked while the completion retries are 
+    * performed. If the completion retry limit is reached, the transaction
+    * manager abandons the retries and throws a heuristic hazard exception.  
+    * 
+    * @param maxCompletionRetries the completion retry limit.
+    */
+   public void setCompletionRetryLimit(int maxCompletionRetries)
+   {
+      this.completionRetryLimit = maxCompletionRetries;
+   }
+   
+   /**
+    * Gets the completion retry limit. This is the maximum number of times that
+    * the transaction manager retries a completion operation (either commit or
+    * rollback) on a resource (either a remote <code>Resource</code> or an 
+    * <code>XAResource</code>) that raised a transient exception. Whoever called 
+    * the transaction manager is blocked while the completion retries are 
+    * performed. If the completion retry limit is reached, the transaction
+    * manager abandons the retries and throws a heuristic hazard exception.  
+    * 
+    * @return the completion retry limit.
+    */
+   public int getCompletionRetryLimit()
+   {
+      return completionRetryLimit;
+   }
+   
+   /**
+    * Sets the completion retry timeout. The completion retry timeout is the 
+    * number of seconds that the transaction manager waits before retrying a 
+    * completion operation (either commit or rollback) on a resource (either a
+    * remote <code>Resource</code> or an <code>XAResource</code>) that raised a 
+    * transient exception. This is a blocking timeout (whoever called the 
+    * transaction manager is blocked until the commit or rollback is retried)
+    * and is applicable if the transaction manager did not report a heuristic
+    * hazard for the transaction. If a heuristic hazard has been reported, then
+    * the applicable timouts are the non-blocking ones: the XA retry timeout and 
+    * the prepared timeout.    
+    * 
+    * @param seconds the timeout (in seconds) for retrying a completion 
+    *                operation after a transient exception and before the 
+    *                transaction manager reports a heuristic hazard. 
+    */
+   public void setCompletionRetryTimeout(int seconds)
+   {
+      this.completionRetryTimeout = 1000 * seconds;
+   }
+   
+   /**
+    * Gets the completion retry timeout. The completion retry timeout is the 
+    * number of seconds that the transaction manager waits before retrying a 
+    * completion operation (either commit or rollback) on a resource (either a
+    * remote <code>Resource</code> or an <code>XAResource</code>) that raised a 
+    * transient exception. This is a blocking timeout (whoever called the 
+    * transaction manager is blocked until the commit or rollback is retried)
+    * and is applicable if the transaction manager did not report a heuristic
+    * hazard for the transaction. If a heuristic hazard has been reported, then
+    * the applicable timouts are the non-blocking ones: the XA retry timeout and 
+    * the prepared timeout.    
+    *
+    * @return the timeout (in seconds) for retrying a completion operation 
+    *         after a transient exception and before the transaction manager 
+    *         reports a heuristic hazard.
+    */
+   public int getCompletionRetryTimeout()
+   {
+      return completionRetryTimeout / 1000;
+   }
+
+   /**
+    * Gets the completion retry timeout in milliseconds.
+    * 
+    * @return the timeout (in milliseconds) for retrying a completion operation 
+    *         after a transient exception and before the transaction manager 
+    *         reports a heuristic hazard.
+    */
+   public int getCompletionRetryTimeoutMillis()
+   {
+      return xaRetryTimeout;
+   }
+   
+   /**
+    * Sets the XA retry timeout.
+    * 
+    * @param seconds the timeout (in seconds) for retrying operations on XA 
+    *                resources.
+    */
+   public void setXARetryTimeout(int seconds)
+   {
+      this.xaRetryTimeout = 1000 * seconds;
+   }
+   
+   /**
+    * Gets the XA retry timeout.
+    * 
+    * @return the timeout (in seconds) for retrying operations on XA resources.
+    */
+   public int getXARetryTimeout()
+   {
+      return xaRetryTimeout / 1000;
+   }
+   
+   /**
+    * Gets the XA retry timeout in milliseconds.
+    * 
+    * @return the timeout (in milliseconds) for retrying operations on XA
+    *         resources.
+    */
+   public int getXARetryTimeoutMillis()
+   {
+      return xaRetryTimeout;
+   }
+   
+   /**
+    * Sets the prepared timeout. A transaction branch that is the prepared 
+    * state waits for an amount of time equal to the prepared timeout before 
+    * generating a call to <code>replayCompletion</code> on its recovery
+    * coordinator.
+    * 
+    * @param seconds the timeout (in seconds) for a transaction branch in the
+    *                the prepared state.
+    */
+   public void setPreparedTimeout(int seconds)
+   {
+      this.preparedTimeout = 1000 * seconds;
+   }
+   
+   /**
+    * Gets the prepared timeout. A transaction branch that is the prepared 
+    * state waits for an amount of time equal to the prepared timeout before 
+    * generating a call to <code>replayCompletion</code> on its recovery
+    * coordinator.
+    * 
+    * @return the timeout (in seconds) for a transaction branch in the prepared 
+    *         state.
+    */
+   public int getPreparedTimeout()
+   {
+      return preparedTimeout / 1000;
+   }
+   
+   /**
+    * Gets the prepared timeout in milliseconds.
+    * 
+    * @return the timeout (in milliseconds) for a transaction branch in the 
+    *         prepared state.
+    */
+  public int getPreparedTimeoutMillis()
+   {
+      return preparedTimeout;
+   }
+   
+  /**
+   * Sets the boolean attribute "root branch remembers heuristic decisions".
+   * If this attribute is true, the root branch remembers a heuristically
+   * completed transaction until explicitly told (through a call to the MBean
+   * operation <code>forget</code>) to forget that transaction. If this 
+   * attribute is false, the root branch immediately forgets a transaction
+   * when the transaction completes.
+   * 
+   * @param newValue true if the root branch should remember heuristic 
+   *                 decisions, false otherwise.
+   */
+  public void setRootBranchRemembersHeuristicDecisions(boolean newValue)
+  {
+     rootBranchRemembersHeuristicDecisions = newValue;
+  }
+  
+  /**
+   * Gets the boolean attribute "root branch remembers heuristic decisions".
+   * If this attribute is true, the root branch remembers a heuristically
+   * completed transaction until explicitly told (through a call to the MBean
+   * operation <code>forget</code>) to forget that transaction. If this 
+   * attribute is false, the root branch immediately forgets a transaction
+   * when the transaction completes.
+   * 
+   * @return true if the root branch remember heuristic decisions, 
+   *         false otherwise.
+   */
+  public boolean isRootBranchRemembersHeuristicDecisions()
+  {
+     return rootBranchRemembersHeuristicDecisions;
+  }
+  
+  /**
+   * Sets the boolean attribute "report heuristic hazard as heuristic mixed".
+   * If this attribute is true, each of the commit methods of the JTA API 
+   * (<code>javax.transaction.Transaction.commit()</code>, 
+   * <code>javax.transaction.TransactionManager.commit()</code>, and
+   * <code>javax.transaction.UserTransaction.commit()</code>) throws a
+   * <code>HeuristicMixedException</code> to report a heuristic hazard to its 
+   * caller. If the attribute is false, those methods do not report heuristic
+   * hazards to their callers. In any case, transactions with heuristic hazard
+   * status are listed by the MBean operation 
+   * <code>listHeuristicallyCompletedTransactions()</code>.
+   * 
+   * @param newValue true if a JTA commit should throw 
+   *         <code>HeuristicMixedException</code> to report a heuristic hazard
+   *         to its caller, or false if a JTA commit should not report a 
+   *         heuristic hazard to its caller.
+   */
+  public void setReportHeuristicHazardAsHeuristicMixed(boolean newValue)
+  {
+     reportHeuristicHazardAsHeuristicMixed = newValue;
+  }
+  
+  /**
+   * Gets the boolean attribute "report heuristic hazard as heuristic mixed".
+   * If this attribute is true, each of the commit methods of the JTA API 
+   * (<code>javax.transaction.Transaction.commit()</code>, 
+   * <code>javax.transaction.TransactionManager.commit()</code>, and
+   * <code>javax.transaction.UserTransaction.commit()</code>) throws a
+   * <code>HeuristicMixedException</code> to report a heuristic hazard to its 
+   * caller. If the attribute is false, those methods do not report heuristic
+   * hazards to their callers. In any case, transactions with heuristic hazard
+   * status are listed by the MBean operation 
+   * <code>listHeuristicallyCompletedTransactions()</code>.
+   *  
+   * @return true if a JTA commit throws <code>HeuristicMixedException</code>
+   *         to report a heuristic hazard to its caller, or false if a JTA
+   *         commit does not report a heuristic hazard to its caller. 
+   */
+  public boolean isReportHeuristicHazardAsHeuristicMixed()
+  {
+     return reportHeuristicHazardAsHeuristicMixed;
+  }
+  
+   /**
+    * Begin a new transaction.
+    * The new transaction will be associated with the calling thread.
+    */
+   public void begin()
+         throws NotSupportedException, 
+                SystemException
+   {
+      trace = log.isTraceEnabled(); // hack!
+
+      ThreadInfo ti = getThreadInfo();
+      TransactionImpl current = ti.tx;
+
+      if (current != null)
+      {
+         if (current.isDone())
+            disassociateThread(ti);
+         else
+            throw new NotSupportedException
+            ("Transaction already active, cannot nest transactions.");
+      }
+
+      long timeout = (ti.timeout == 0) ? timeOut : ti.timeout;
+      TransactionImpl tx = new TransactionImpl(timeout);
+      associateThread(ti, tx);
+      localIdTx.put(tx.getLocalId(), tx);
+      if (globalIdsEnabled)
+         globalIdTx.put(tx.getGlobalId(), tx);
+
+      if (trace)
+         log.trace("began tx: " + tx);
+   }
+
+   /**
+    * Commit the transaction associated with the currently running thread.
+    */
+   public void commit()
+         throws RollbackException,
+                HeuristicMixedException,
+                HeuristicRollbackException,
+                SecurityException,
+                IllegalStateException,
+                SystemException
+   {
+      ThreadInfo ti = getThreadInfo();
+      TransactionImpl current = ti.tx;
+
+      if (current != null)
+      {
+         current.commit();
+         disassociateThread(ti);
+         if (trace)
+            log.trace("commited tx: " + current);
+      }
+      else
+         throw new IllegalStateException("No transaction.");
+   }
+
+   /**
+    * Return the status of the transaction associated with the currently
+    * running thread, or <code>Status.STATUS_NO_TRANSACTION</code> if no
+    * active transaction is currently associated.
+    */
+   public int getStatus() 
+         throws SystemException
+   {
+      ThreadInfo ti = getThreadInfo();
+      TransactionImpl current = ti.tx;
+
+      if (current != null)
+      {
+         if (current.isDone())
+            disassociateThread(ti);
+         else
+            return current.getStatus();
+      }
+      return Status.STATUS_NO_TRANSACTION;
+   }
+
+   /**
+    * Return the transaction currently associated with the invoking thread,
+    * or <code>null</code> if no active transaction is currently associated.
+    */
+   public Transaction getTransaction()
+         throws SystemException
+   {
+      ThreadInfo ti = getThreadInfo();
+      TransactionImpl current = ti.tx;
+
+      if (current != null && current.isDone())
+      {
+         current = null;
+         disassociateThread(ti);
+      }
+
+      return current;
+   }
+
+   /**
+    * Resume a transaction.
+    * <p/>
+    * Note: This will not enlist any resources involved in this
+    * transaction. According to JTA1.0.1 specification section 3.2.3,
+    * that is the responsibility of the application server.
+    */
+   public void resume(Transaction transaction)
+         throws InvalidTransactionException,
+                IllegalStateException,
+                SystemException
+   {
+      if (transaction != null && !(transaction instanceof TransactionImpl))
+         throw new RuntimeException("Not a TransactionImpl, but a " +
+         transaction.getClass().getName());
+
+      ThreadInfo ti = getThreadInfo();
+      TransactionImpl current = ti.tx;
+
+      if (current != null)
+      {
+         if (current.isDone())
+            current = ti.tx = null;
+         else
+            throw new IllegalStateException("Already associated with a tx");
+      }
+
+      if (current != transaction)
+      {
+         associateThread(ti, (TransactionImpl) transaction);
+      }
+
+      if (trace)
+         log.trace("resumed tx: " + ti.tx);
+   }
+
+   /**
+    * Suspend the transaction currently associated with the current
+    * thread, and return it.
+    * <p/>
+    * Note: This will not delist any resources involved in this
+    * transaction. According to JTA1.0.1 specification section 3.2.3,
+    * that is the responsibility of the application server.
+    */
+   public Transaction suspend()
+         throws SystemException
+   {
+      ThreadInfo ti = getThreadInfo();
+      TransactionImpl current = ti.tx;
+
+      if (current != null)
+      {
+         current.disassociateCurrentThread();
+         ti.tx = null;
+
+         if (trace)
+            log.trace("suspended tx: " + current);
+
+         if (current.isDone())
+            current = null;
+      }
+
+      return current;
+   }
+
+   /**
+    * Roll back the transaction associated with the currently running thread.
+    */
+   public void rollback()
+         throws IllegalStateException, 
+                SecurityException, 
+                SystemException
+   {
+      ThreadInfo ti = getThreadInfo();
+      TransactionImpl current = ti.tx;
+
+      if (current != null)
+      {
+         if (!current.isDone())
+         {
+            current.rollback();
+
+            if (trace)
+               log.trace("rolled back tx: " + current);
+            return;
+         }
+         disassociateThread(ti);
+      }
+      throw new IllegalStateException("No transaction.");
+   }
+
+   /**
+    * Mark the transaction associated with the currently running thread
+    * so that the only possible outcome is a rollback.
+    */
+   public void setRollbackOnly()
+         throws IllegalStateException, 
+                SystemException
+   {
+      ThreadInfo ti = getThreadInfo();
+      TransactionImpl current = ti.tx;
+
+      if (current != null)
+      {
+         if (!current.isDone())
+         {
+            current.setRollbackOnly();
+
+            if (trace)
+               log.trace("tx marked for rollback only: " + current);
+            return;
+         }
+         ti.tx = null;
+      }
+      throw new IllegalStateException("No transaction.");
+   }
+
+   public int getTransactionTimeout()
+   {
+      return (int) (getThreadInfo().timeout / 1000);
+   }
+
+   /**
+    * Set the transaction timeout for new transactions started by the
+    * calling thread.
+    */
+   public void setTransactionTimeout(int seconds)
+         throws SystemException
+   {
+      getThreadInfo().timeout = 1000 * seconds;
+
+      if (trace)
+         log.trace("tx timeout is now: " + seconds + "s");
+   }
+
+   /**
+    * Set the default transaction timeout for new transactions.
+    * This default value is used if <code>setTransactionTimeout()</code>
+    * was never called, or if it was called with a value of <code>0</code>.
+    */
+   public void setDefaultTransactionTimeout(int seconds)
+   {
+      timeOut = 1000L * seconds;
+
+      if (trace)
+         log.trace("default tx timeout is now: " + seconds + "s");
+   }
+
+   /**
+    * Get the default transaction timeout.
+    *
+    * @return Default transaction timeout in seconds.
+    */
+   public int getDefaultTransactionTimeout()
+   {
+      return (int) (timeOut / 1000);
+   }
+
+   public long getTimeLeftBeforeTransactionTimeout(boolean errorRollback) throws RollbackException
+   {
+      try
+      {
+         ThreadInfo ti = getThreadInfo();
+         TransactionImpl current = ti.tx;
+         if (current != null && current.isDone())
+         {
+            disassociateThread(ti);
+            return -1;
+         }
+         return current.getTimeLeftBeforeTimeout(errorRollback);
+      }
+      catch (RollbackException e)
+      {
+         throw e;
+      }
+      catch (Exception ignored)
+      {
+         return -1;
+      }
+   }
+
+   /**
+    * The following 2 methods are here to provide association and
+    * disassociation of the thread.
+    */
+   public Transaction disassociateThread()
+   {
+      return disassociateThread(getThreadInfo());
+   }
+
+   private Transaction disassociateThread(ThreadInfo ti)
+   {
+      TransactionImpl current = ti.tx;
+      ti.tx = null;
+      current.disassociateCurrentThread();
+      return current;
+   }
+
+   public void associateThread(Transaction transaction)
+   {
+      if (transaction != null && !(transaction instanceof TransactionImpl))
+         throw new RuntimeException("Not a TransactionImpl, but a " +
+         transaction.getClass().getName());
+
+      // Associate with the thread
+      TransactionImpl transactionImpl = (TransactionImpl) transaction;
+      ThreadInfo ti = getThreadInfo();
+      ti.tx = transactionImpl;
+      transactionImpl.associateCurrentThread();
+   }
+
+   private void associateThread(ThreadInfo ti, TransactionImpl transaction)
+   {
+      // Associate with the thread
+      ti.tx = transaction;
+      transaction.associateCurrentThread();
+   }
+
+   /**
+    * Return the number of active transactions
+    */
+   public int getTransactionCount()
+   {
+      return localIdTx.size();
+   }
+
+   /**
+    * A count of the transactions that have been committed
+    */
+   public long getCommitCount()
+   {
+      return commitCount;
+   }
+
+   /**
+    * A count of the transactions that have been rolled back
+    */
+   public long getRollbackCount()
+   {
+      return rollbackCount;
+   }
+
+   /**
+    * Lists the in-doubt transactions. 
+    * 
+    * @return a string with a text listing of in-doubt transactions. 
+    */
+   public String listInDoubtTransactions()
+   {
+      // TODO
+      throw new org.jboss.util.NotImplementedException();
+   }
+   
+   /**
+    * Heuristically commits an in-doubt transaction.
+    * 
+    * @param localTransactionId the local id of the in-doubt transaction to be
+    *                           heuristically committed.
+    */
+   public void heuristicallyCommit(long localTransactionId)
+   {
+      // TODO
+      throw new org.jboss.util.NotImplementedException();
+   }
+   
+   /**
+    * Heuristically commits all in-doubt transactions.
+    */
+  public  void heuristicallyCommitAll()
+  {
+     // TODO
+     throw new org.jboss.util.NotImplementedException();
+  }
+   
+   /**
+    * Heuristically rolls back an in-doubt transaction.
+    * 
+    * @param localTransactionId the local id of the in-doubt transaction to be
+    *                           heuristically rolled back.
+    */
+   public void heuristicallyRollback(long localTransactionId)
+   {
+      // TODO
+      throw new org.jboss.util.NotImplementedException();
+   }
+   
+   /**
+    * Heuristically rolls back all in-doubt transactions.
+    */
+   public void heuristicallyRollbackAll()
+   {
+      // TODO
+      throw new org.jboss.util.NotImplementedException();
+   }
+   
+   /**
+    * Lists the heuristically completed transactions coordinated by this
+    * transaction manager. A transaction that was heuristically completed
+    * by a call to <code>heuristicallyCommit(long localTransactionId)</code>,
+    * <code>heuristicallyCommitAll()</code>,
+    * <code>heuristicallyRollback(long localTransactionId)</code>, or
+    * <code>heuristicallyRollbackAll()</code> does not appear in the listing, 
+    * as that transaction had a foreign coordinator. 
+    * 
+    * @return a string with a text listing of heuristically completed
+    *         transactions.
+    */
+   public String listHeuristicallyCompletedTransactions()
+   {
+      // TODO
+      throw new org.jboss.util.NotImplementedException();
+   }
+   
+   /**
+    * Forgets a heuristically completed transaction coordinated by this 
+    * transaction manager.
+    * 
+    * @param localTransactionId the local id of a heuristically completed 
+    *                           transaction coordinated by this transaction 
+    *                           manager.
+    */
+   public void forget(long localTransactionId)
+   {
+      // TODO
+      throw new org.jboss.util.NotImplementedException();
+   }
+   
+   /**
+    * Forgets all heuristically completed transactions coordinated by this 
+    * transaction manager.
+    */
+   public void forgetAll()
+   {
+      // TODO
+      throw new org.jboss.util.NotImplementedException();
+   }
+
+   // Implements TransactionPropagationContextImporter ---------------
+
+   /**
+    * Import a transaction propagation context into this TM.
+    * The TPC is loosely typed, as we may (at a later time) want to
+    * import TPCs that come from other transaction domains without
+    * offloading the conversion to the client.
+    *
+    * @param tpc The transaction propagation context that we want to
+    *            import into this TM. Currently this is an instance
+    *            of LocalId. At some later time this may be an instance
+    *            of a transaction propagation context from another
+    *            transaction domain like
+    *            org.omg.CosTransactions.PropagationContext.
+    * @return a transaction representing this transaction propagation
+    *         context, or null if this TPC cannot be imported.
+    */
+   public Transaction importTransactionPropagationContext(Object tpc)
+   {
+      if (tpc instanceof LocalId)
+      {
+         LocalId id = (LocalId) tpc;
+         return (Transaction) localIdTx.get(id);
+      }
+      else if (tpc instanceof GlobalId && globalIdsEnabled)
+      {
+         GlobalId id = (GlobalId) tpc;
+         Transaction tx = (Transaction) globalIdTx.get(id);
+         if (trace)
+         {
+            if (tx != null)
+               log.trace("Successfully imported transaction context " + tpc);
+            else
+               log.trace("Could not import transaction context " + tpc);
+         }
+         return tx;
+      }
+      else if (tpc instanceof TxPropagationContext && dtmEnabled)
+      {
+         TxPropagationContext fullTpc = (TxPropagationContext) tpc;
+         Transaction tx = importExternalTransaction(fullTpc.formatId, 
+                                                    fullTpc.globalId,
+                                                    fullTpc.coordinator,
+                                                    fullTpc.timeout * 1000);
+         if (trace)
+         {
+            if (tx != null)
+               log.trace("Successfully imported transaction context " + tpc);
+            else
+               log.trace("Could not import transaction context " + tpc);
+         }
+         return tx;
+      }  
+      log.warn("Cannot import transaction propagation context: " + tpc);
+      return null;
+   }
+
+   // Implements TransactionPropagationContextFactory ---------------
+
+   /**
+    * Return a TPC for the current transaction.
+    */
+   public Object getTransactionPropagationContext()
+   {
+      return getTransactionPropagationContext(getThreadInfo().tx);
+   }
+
+   /**
+    * Return a TPC for the argument transaction.
+    */
+   public Object getTransactionPropagationContext(Transaction t)
+   {
+      // If no transaction or unknown transaction class, return null.
+      if (t == null)
+         return null;
+      if (!(t instanceof TransactionImpl))
+      {
+         log.warn("Cannot export transaction propagation context: " + t);
+         return null;
+      }
+      
+      TransactionImpl tx = (TransactionImpl) t;
+         
+      if (!dtmEnabled)
+      {
+         return tx.getLocalId();
+      }
+      else
+      {
+         return tx.getPropagationContext();
+      }
+   }
+
+   // Implements XATerminator ----------------------------------
+   
+   public void registerWork(Work work, Xid xid, long timeout)
+         throws WorkCompletedException
+   {
+      if (trace)
+         log.trace("registering work=" + work + " xid=" + xid + 
+                   " timeout=" + timeout);
+      try
+      {
+         TransactionImpl tx = importExternalTransaction(xid, timeout);
+         tx.setWork(work);
+      }
+      catch (WorkCompletedException e)
+      {
+         throw e;
+      }
+      catch (Throwable t)
+      {
+         WorkCompletedException e = 
+            new WorkCompletedException("Error registering work", t);
+         e.setErrorCode(WorkException.TX_RECREATE_FAILED);
+         throw e;
+      }
+      if (trace)
+         log.trace("registered work= " + work + " xid=" + xid + 
+                   " timeout=" + timeout);
+   }
+
+   public void startWork(Work work, Xid xid)
+         throws WorkCompletedException
+   {
+      if (trace)
+         log.trace("starting work=" + work + " xid=" + xid);
+      TransactionImpl tx = getExternalTransaction(xid);
+      associateThread(tx);
+      if (trace)
+         log.trace("started work= " + work + " xid=" + xid);
+   }
+
+   public void endWork(Work work, Xid xid)
+   {
+      if (trace)
+         log.trace("ending work=" + work + " xid=" + xid);
+      try
+      {
+         TransactionImpl tx = getExternalTransaction(xid);
+         tx.setWork(null);
+         disassociateThread();
+      }
+      catch (WorkCompletedException e)
+      {
+         log.error("Unexpected error from endWork ", e);
+         throw new UnexpectedThrowable(e.toString());
+      }
+      if (trace)
+         log.trace("ended work=" + work + " xid=" + xid);
+   }
+
+   public void cancelWork(Work work, Xid xid)
+   {
+      if (trace)
+         log.trace("cancling work=" + work + " xid=" + xid);
+      try
+      {
+         TransactionImpl tx = getExternalTransaction(xid);
+         tx.setWork(null);
+      }
+      catch (WorkCompletedException e)
+      {
+         log.error("Unexpected error from cancelWork ", e);
+         throw new UnexpectedThrowable(e.toString());
+      }
+      if (trace)
+         log.trace("cancled work=" + work + " xid=" + xid);
+   }
+
+   public int prepare(Xid xid)
+         throws XAException
+   {
+      if (trace)
+         log.trace("preparing xid=" + xid);
+      try
+      {
+         TransactionImpl tx = getExternalTransaction(xid);
+         resume(tx);
+         int result = tx.prepare(xid);
+         if (trace)
+            log.trace("prepared xid=" + xid + " result=" + result);
+         return result;
+      }
+      catch (Throwable t)
+      {
+         JBossXAException.rethrowAsXAException("Error during prepare", t);
+         throw new UnreachableStatementException();
+      }
+      finally 
+      {
+         try
+         {
+            suspend();
+         }
+         catch (SystemException e)
+         {
+            JBossXAException.rethrowAsXAException("Error during prepare", e);
+            throw new UnreachableStatementException();
+         }
+      }
+   }
+
+   public void rollback(Xid xid)
+         throws XAException
+   {
+      if (trace)
+         log.trace("rolling back xid=" + xid);
+      try
+      {
+         TransactionImpl tx = getExternalTransaction(xid);
+         tx.rollback();
+      }
+      catch (Throwable t)
+      {
+         JBossXAException.rethrowAsXAException("Error during rollback", t);
+      }
+      if (trace)
+         log.trace("rolled back xid=" + xid);
+   }
+
+   public void commit(Xid xid, boolean onePhase)
+         throws XAException
+   {
+      if (trace)
+         log.trace("committing xid=" + xid + " onePhase=" + onePhase);
+      try
+      {
+         TransactionImpl tx = getExternalTransaction(xid);
+         tx.commit(onePhase);
+      }
+      catch (Throwable t)
+      {
+         JBossXAException.rethrowAsXAException("Error during commit", t);
+      }
+      if (trace)
+         log.trace("committed xid=" + xid);
+   }
+
+   public void forget(Xid xid)
+         throws XAException
+   {
+      if (trace)
+         log.trace("forgetting xid=" + xid);
+      try
+      {
+         TransactionImpl tx = getExternalTransaction(xid);
+         tx.forget();
+      }
+      catch (Throwable t)
+      {
+         JBossXAException.rethrowAsXAException("Error during forget", t);
+      }
+      if (trace)
+         log.trace("forgot xid=" + xid);
+   }
+
+   public Xid[] recover(int flag)
+         throws XAException
+   {
+      List xidList = new ArrayList();
+      Collection txCollection = localIdTx.values();
+      synchronized (localIdTx)
+      {
+         Iterator i = txCollection.iterator();
+         while (i.hasNext())
+         {
+            TransactionImpl tx = (TransactionImpl) i.next();
+            if (tx.isPreparedOrHeuristicallyCompletedJCAInboundTx())
+               xidList.add(tx.getInboundXid());
+         }
+      }
+      return (Xid[]) xidList.toArray(new Xid[xidList.size()]);
+   }
+
+   /**
+    * Imports an external transaction. This method is called for foreign
+    * transaction imported through DTM or OTS transaction propagation contexts.
+    */
+   public TransactionImpl importExternalTransaction(int formatId, 
+                                                    byte[] globalId,
+                                                    Coordinator coordinator,
+                                                    long txTimeOut)
+   {
+      GlobalId gid = new GlobalId(formatId, globalId);
+      TransactionImpl tx = (TransactionImpl) globalIdTx.get(gid);
+      if (tx != null)
+      {
+         if (trace)
+            log.trace("imported existing transaction gid: " + gid + 
+                      " tx=" + tx);
+      }
+      else
+      {
+         ThreadInfo ti = getThreadInfo();
+         long timeout = (ti.timeout == 0) ? txTimeOut : ti.timeout;
+         tx = new TransactionImpl(gid, coordinator, timeout);
+         localIdTx.put(tx.getLocalId(), tx);
+         if (globalIdsEnabled)
+            globalIdTx.put(gid, tx);
+
+         if (trace)
+            log.trace("imported new transaction gid: " + gid + " tx=" + tx + 
+                      " timeout=" + timeout);
+      }
+      return tx;
+   }
+
+   /**
+    * Imports an external transaction. This method is called for foreign 
+    * transactions imported through the JCA transaction inflow mechanism.
+    */
+   TransactionImpl importExternalTransaction(Xid xid, long txTimeOut)
+   {
+      GlobalId gid = new GlobalId(xid.getFormatId(), 
+                                  xid.getGlobalTransactionId());
+      TransactionImpl tx = (TransactionImpl) globalIdTx.get(gid);
+      if (tx != null)
+      {
+         if (trace)
+            log.trace("imported existing transaction gid: " + gid + 
+                      " tx=" + tx);
+      }
+      else
+      {
+         ThreadInfo ti = getThreadInfo();
+         long timeout = (ti.timeout == 0) ? txTimeOut : ti.timeout;
+         tx = new TransactionImpl(gid, xid.getBranchQualifier(), timeout);
+         localIdTx.put(tx.getLocalId(), tx);
+         if (globalIdsEnabled)
+            globalIdTx.put(gid, tx);
+
+         if (trace)
+            log.trace("imported new transaction gid: " + gid + " tx=" + tx + 
+                      " timeout=" + timeout);
+      }
+      return tx;
+   }
+
+   TransactionImpl getExternalTransaction(Xid xid)
+         throws WorkCompletedException
+   {
+      GlobalId gid = new GlobalId(xid);
+      TransactionImpl tx = (TransactionImpl) globalIdTx.get(gid);
+      if (tx == null)
+         throw new WorkCompletedException("Xid not found " + xid, 
+                                          WorkException.TX_RECREATE_FAILED);
+      return tx;
+   }
+
+   /**
+    * Recreates a locally-started transaction that does not involve other 
+    * transaction managers. This method is intended to be called at recovery
+    * time, for recreating transactions that were in the committing state when 
+    * the server crashed. Such a transaction completed the first phase of the 
+    * 2PC protocol and logged the commit decision, but it must still send 
+    * commit messages to some or all of its <code>XAResource</code>s.
+    * 
+    * @param localId the local id of a locally-started transaction that is in 
+    *             the committing state and does not involve other transaction 
+    *             managers
+    * @param pendingXAWorkList a list of <code>org.jboss.tm.XAWork</code>
+    *             instances containing one element for each 
+    *             <code>XAResource</code> that is enlisted with the transaction
+    *             and is still in the prepared state
+    * @param completionHandler the 
+    *             <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be 
+    *             notifed when the second phase of the 2PC completes
+    * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+    *             contaning information on a locally-detected heuristic hazard.           
+    */
+   public void recreateTransaction(long localId, 
+                                   List pendingXAWorkList,
+                                   TxCompletionHandler completionHandler,
+                                   LogRecord.HeurData heurData)
+   {
+      LocalId localIdObject = new LocalId(localId);
+      TransactionImpl tx = (TransactionImpl) localIdTx.get(localIdObject);
+      if (tx != null)
+      {
+         if (trace)
+            log.trace("recreateTransaction called for existing transaction, " + 
+                      "localId=" + localId + ", tx=" + tx, 
+                      new Throwable("[Stack trace]"));
+      }
+      else
+      {
+         tx = new TransactionImpl(localId, 
+                                  pendingXAWorkList, 
+                                  completionHandler,
+                                  heurData); 
+         localIdTx.put(tx.getLocalId(), tx);
+         if (globalIdsEnabled)
+            globalIdTx.put(tx.getGlobalId(), tx);
+
+         if (trace)
+            log.trace("recreated transaction with localId=" + localId + 
+                      ", tx=" + tx +
+                      ", status=" + TxUtils.getStatusAsString(tx.getStatus()));
+      }
+   }
+
+   /**
+    * Recreates a locally-started transaction that involves other transaction 
+    * managers. Involving other transaction managers means that there are 
+    * remote <code>Resource</code>s enlisted with the transaction. This method 
+    * is intended to be called at recovery time, for recreating transactions 
+    * that were in the committing state when the server crashed. Such a 
+    * transaction completed the first phase of the 2PC protocol and logged the
+    * commit decision, but it must still send commit messages to some or all of
+    * its resources (<code>XAResource</code>s and remote 
+    * <code>Resource</code>s).
+    * 
+    * @param localId the local id of a locally-started transaction that is in 
+    *             the committing state and involves other transaction managers
+    * @param pendingXAWorkList a list of <code>org.jboss.tm.XAWork</code>
+    *             instances containing one element for each 
+    *             <code>XAResource</code> that is enlisted with the transaction 
+    *             and is still in the prepared state
+    * @param resources an array with stringfied references for the remote 
+    *             <code>Resource</code>s enlisted with the transaction 
+    * @param completionHandler the 
+    *             <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be 
+    *             notifed when the second phase of the 2PC completes
+    * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+    *             contaning information on a locally-detected heuristic hazard.           
+    */
+   public void recreateTransaction(long localId, 
+                                   List pendingXAWorkList,
+                                   String[] resources,
+                                   TxCompletionHandler completionHandler,
+                                   LogRecord.HeurData heurData)
+   {
+      LocalId localIdObject = new LocalId(localId);
+      TransactionImpl tx = (TransactionImpl) localIdTx.get(localIdObject);
+      if (tx != null)
+      {
+         if (trace)
+            log.trace("recreateTransaction called for existing transaction, " + 
+                      "localId=" + localId + ", tx=" + tx, 
+                      new Throwable("[Stack trace]"));
+      }
+      else
+      {
+         tx = new TransactionImpl(localId, 
+                                  pendingXAWorkList,
+                                  resources,
+                                  completionHandler,
+                                  heurData); 
+         localIdTx.put(tx.getLocalId(), tx);
+         if (globalIdsEnabled)
+            globalIdTx.put(tx.getGlobalId(), tx);
+
+         if (trace)
+            log.trace("recreated transaction with localId=" + localId + 
+                      ", tx=" + tx +
+                      ", status=" + TxUtils.getStatusAsString(tx.getStatus()));
+      }
+   }
+   
+   /**
+    * Recreates a foreign transaction that entered this virtual machine in a
+    * transaction context propagated along with a remote method invocation. 
+    * This method is intended to be called at recovery time, for recreating 
+    * transactions that were in the prepared state when the server crashed. 
+    * 
+    * @param localId the local id of a foreign transaction that entered this 
+    *             virtual machine in a transaction propagation context and is
+    *             propagated along with a remote method invocation and is in 
+    *             the prepared state
+    * @param inboundFormatId format id part of the foreign transaction 
+    *             identifier that entered this virtual machine
+    * @param globalTransactionId global id part of the foreign transaction 
+    *             identifier that entered this virtual machine
+    * @param recoveryCoord an stringfied reference to the transaction branch's
+    *             <code>RecovertyCoordinator</code>
+    * @param pendingXAWorkList a list of <code>org.jboss.tm.XAWork</code>
+    *             instances containing one element for each 
+    *             <code>XAResource</code> enlisted with the transaction 
+    * @param resources an array with stringfied references for the remote 
+    *             <code>Resource</code>s enlisted with the transaction 
+    * @param completionHandler the 
+    *             <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be 
+    *             notifed when the second phase of the 2PC completes
+    * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+    *             contaning information on a locally-detected heuristic hazard.           
+    */
+   public void recreateTransaction(long localId, 
+                                   int inboundFormatId,
+                                   byte[] globalTransactionId,
+                                   String recoveryCoord,
+                                   List pendingXAWorkList,
+                                   String[] resources,
+                                   TxCompletionHandler completionHandler,
+                                   LogRecord.HeurData heurData)
+   {
+      LocalId localIdObject = new LocalId(localId);
+      TransactionImpl tx = (TransactionImpl) localIdTx.get(localIdObject);
+      if (tx != null)
+      {
+         if (trace)
+            log.trace("recreateTransaction called for existing transaction, " + 
+                      "localId=" + localId + ", tx=" + tx, 
+                      new Throwable("[Stack trace]"));
+      }
+      else
+      {
+         tx = new TransactionImpl(localId, 
+                                  inboundFormatId,
+                                  globalTransactionId,
+                                  recoveryCoord,
+                                  pendingXAWorkList,
+                                  resources,
+                                  completionHandler,
+                                  heurData); 
+         localIdTx.put(tx.getLocalId(), tx);
+         if (globalIdsEnabled)
+            globalIdTx.put(tx.getGlobalId(), tx);
+
+         if (trace)
+            log.trace("recreated transaction with localId=" + localId + 
+                      " tx=" + tx +
+                      ", status=" + TxUtils.getStatusAsString(tx.getStatus()));
+      }
+   }
+   
+   /**
+    * Recreates a foreign transaction that entered this virtual machine through
+    * JCA transaction inflow. This method is intended to be called at recovery
+    * time, for recreating transactions that were in the prepared state when 
+    * the server crashed.  
+    * 
+    * @param localId the local id of a foreign transaction that entered this 
+    *             virtual machine in a transaction propagation context and is
+    *             propagated along with a remote method invocation and is in 
+    *             the prepared state
+    * @param inboundFormatId format id part of the foreign <code>Xid</code> 
+    * @param globalTransactionId global id part of the foreign <code>Xid</code> 
+    * @param inboundBranchQualifier the branch qualifier part of the foreign 
+    *             <code>Xid</code> 
+    * @param pendingXAWorkList a list of <code>org.jboss.tm.XAWork</code>
+    *             instances containing one element for each 
+    *             <code>XAResource</code> enlisted with the transaction 
+    * @param resources an array with stringfied references for the remote 
+    *             <code>Resource</code>s enlisted with the transaction 
+    * @param completionHandler the 
+    *             <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be 
+    *             notifed when the second phase of the 2PC completes
+    * @param heurData either null or a <code>LogRecord.HeurData</code> instance
+    *             contaning information on a locally-detected heuristic hazard.           
+    */
+   public void recreateTransaction(long localId,
+                                   int inboundFormatId,
+                                   byte[] globalTransactionId,
+                                   byte[] inboundBranchQualifier,
+                                   List pendingXAWorkList,
+                                   String[] resources,
+                                   TxCompletionHandler completionHandler,
+                                   LogRecord.HeurData heurData)
+   {
+      LocalId localIdObject = new LocalId(localId);
+      TransactionImpl tx = (TransactionImpl) localIdTx.get(localIdObject);
+      if (tx != null)
+      {
+         if (trace)
+            log.trace("recreateTransaction called for existing transaction, " + 
+                      "localId=" + localId + ", tx=" + tx, 
+                      new Throwable("[Stack trace]"));
+      }
+      else
+      {
+         tx = new TransactionImpl(localId, 
+                                  inboundFormatId,
+                                  globalTransactionId,
+                                  inboundBranchQualifier,
+                                  pendingXAWorkList,
+                                  resources,
+                                  completionHandler,
+                                  heurData); 
+         localIdTx.put(tx.getLocalId(), tx);
+         if (globalIdsEnabled)
+            globalIdTx.put(tx.getGlobalId(), tx);
+
+         if (trace)
+            log.trace("recreated transaction with localId=" + localId + 
+                      ", tx=" + tx +
+                      ", status=" + TxUtils.getStatusAsString(tx.getStatus()));
+      }
+   }
+
+   /**
+    * Recreates a transaction that is in a heuristically completed state 
+    * (either committed or rolledback). This method is intended to be called 
+    * at recovery time, for recreating heuristically completed transactions 
+    * that were not yet forgotten when the server crashed.  
+    * 
+    * @param heurData an instance of <code>LogRecord.HeurData</code> with
+    *             information on the heuristically completed transaction 
+    * @param xaResourcesWithHeuristics a list of 
+    *             <code>org.jboss.tm.XAWork</code>
+    *             instances containing one element for each 
+    *             <code>XAResource</code> that is in a a heuristic status
+    * @param completionHandler the 
+    *             <code>org.jboss.tm.recovery.TxCompletionHandler</code> to be 
+    *             notifed when the second phase of the 2PC completes.
+    */
+   public void recreateTransaction(LogRecord.HeurData heurData,
+                                   List xaResourcesWithHeuristics,
+                                   TxCompletionHandler completionHandler)
+   {
+      LocalId localIdObject = new LocalId(heurData.localTransactionId);
+      TransactionImpl tx = (TransactionImpl) localIdTx.get(localIdObject);
+      if (tx != null)
+      {
+         if (trace)
+            log.trace("recreateTransaction called for existing transaction, " + 
+                      "localId=" + heurData.localTransactionId + ", tx=" + tx +
+                      ", status=" + TxUtils.getStatusAsString(tx.getStatus()) +
+                      ", heuristicStatus=" + TxUtils.getXAErrorCodeAsString(
+                                                 heurData.heuristicStatusCode), 
+                      new Throwable("[Stack trace]"));
+      }
+      else
+      {
+         tx = new TransactionImpl(heurData, 
+                                  xaResourcesWithHeuristics,
+                                  completionHandler); 
+         localIdTx.put(tx.getLocalId(), tx);
+         if (globalIdsEnabled)
+            globalIdTx.put(tx.getGlobalId(), tx);
+
+         if (trace)
+            log.trace("recreated transaction with localId=" + 
+                      heurData.localTransactionId + ", tx=" + tx +
+                      ", status=" + TxUtils.getStatusAsString(tx.getStatus()) +
+                      ", heuristicStatus=" + TxUtils.getXAErrorCodeAsString(
+                                                 heurData.heuristicStatusCode));
+
+         //if (!tx.isImported())
+         //{
+         //   tx.forget();
+         //}
+      }
+   }
+   
+   // Implements TransactionLocalDelegate ----------------------
+
+   public void lock(TransactionLocal local, Transaction tx) throws InterruptedException
+   {
+      TransactionImpl tximpl = (TransactionImpl) tx;
+      tximpl.lock();
+   }
+
+   public void unlock(TransactionLocal local, Transaction tx)
+   {
+      TransactionImpl tximpl = (TransactionImpl) tx;
+      tximpl.unlock();
+   }
+
+   public Object getValue(TransactionLocal local, Transaction tx)
+   {
+      TransactionImpl tximpl = (TransactionImpl) tx;
+      return tximpl.getTransactionLocalValue(local);
+   }
+
+   public void storeValue(TransactionLocal local, Transaction tx, Object value)
+   {
+      TransactionImpl tximpl = (TransactionImpl) tx;
+      tximpl.putTransactionLocalValue(local, value);
+   }
+
+   public boolean containsValue(TransactionLocal local, Transaction tx)
+   {
+      TransactionImpl tximpl = (TransactionImpl) tx;
+      return tximpl.containsTransactionLocal(local);
+   }
+
+   // Implements StringRemoteRefConverter ----------------------------
+
+   public Resource stringToResource(String strResource)
+   {
+      return TransactionImpl.stringToResource(strResource);
+   }
+
+   public RecoveryCoordinator stringToRecoveryCoordinator(
+                                                   String strRecCoordinator)
+   {
+      return TransactionImpl.stringToRecoveryCoordinator(strRecCoordinator);
+   }
+
+   public String resourceToString(Resource res)
+   {
+      return TransactionImpl.resourceToString(res);
+   }
+
+   public String recoveryCoordinatorToString(RecoveryCoordinator recoveryCoord)
+   {
+      return TransactionImpl.recoveryCoordinatorToString(recoveryCoord);
+   }
+
+   // Attribute getters and setters ----------------------------------
+
+   /**
+    * Setter for attribute <code>dtmEnabled</code>.
+    */
+   public void setDTMEnabled(boolean newValue)
+   {
+      if (newValue == true && !globalIdsEnabled)
+      {
+         // DTM requires global ids
+         setGlobalIdsEnabled(newValue);
+      }
+      dtmEnabled = newValue;
+   }
+
+   /**
+    * Setter for the DTM <code>CoordinatorFactory</code>.
+    */
+   public void setDTMCoordinatorFactory(
+         CoordinatorFactory dtmCoordinatorFactory)
+   {
+      TransactionImpl.setDTMCoordinatorFactory(dtmCoordinatorFactory);
+   }
+
+   /**
+    * Setter for the DTM <code>ResourceFactory</code>.
+    */
+   public void setDTMResourceFactory(ResourceFactory dtmResourceFactory)
+   {
+      TransactionImpl.setDTMResourceFactory(dtmResourceFactory);
+   }
+
+   /**
+    * Setter for the OTS <code>ResourceFactory</code>.
+    */
+   public void setOTSResourceFactory(ResourceFactory otsResourceFactory)
+   {
+      TransactionImpl.setOTSResourceFactory(otsResourceFactory);
+   }
+
+   /**
+    * Setter for the <code>OTSContextFactory</code>.
+    */
+   public void setOTSContextFactory(OTSContextFactory otsContextFactory)
+   {
+      TransactionImpl.setOTSContextFactory(otsContextFactory);
+   }
+
+   /**
+    * Setter for the <code>interpositionEnabled</code> flag.
+    */
+   public void setInterpositionEnabled(boolean newValue)
+   {
+      TransactionImpl.setInterpositionEnabled(newValue);
+   }
+
+   /**
+    * Getter for the <code>interpositionEnabled</code> flag.
+    */
+   public boolean getInterpositionEnabled()
+   {
+      return TransactionImpl.getInterpositionEnabled();
+   }
+   
+   public void setDTMStringRemoteRefConverter(
+                           StringRemoteRefConverter otsStrRemoteRefConverter)
+   {
+      TransactionImpl.setDTMStrRemoteRefConverter(otsStrRemoteRefConverter);
+   }
+
+   public void setOTSStringRemoteRefConverter(
+                           StringRemoteRefConverter otsStrRemoteRefConverter)
+   {
+      TransactionImpl.setOTSStrRemoteRefConverter(otsStrRemoteRefConverter);
+   }
+
+   // Package protected ---------------------------------------------
+
+   /**
+    * Release the given TransactionImpl.
+    */
+   void releaseTransactionImpl(TransactionImpl tx)
+   {
+      localIdTx.remove(tx.getLocalId());
+      if (globalIdsEnabled)
+         globalIdTx.remove(tx.getGlobalId());
+   }
+
+   /**
+    * Increment the commit count
+    */
+   void incCommitCount()
+   {
+      ++commitCount;
+   }
+
+   /**
+    * Increment the rollback count
+    */
+   void incRollbackCount()
+   {
+      ++rollbackCount;
+   }
+
+   // Protected -----------------------------------------------------
+
+   // Private -------------------------------------------------------
+
+   /**
+    * This keeps track of the thread association with transactions
+    * and timeout values.
+    * In some cases terminated transactions may not be cleared here.
+    */
+   private ThreadLocal threadTx = new ThreadLocal();
+
+   /**
+    * This map contains the active transactions as values.
+    * The keys are the <code>LocalId</code>s of the transactions.
+    */
+   private Map localIdTx = Collections.synchronizedMap(new HashMap());
+
+
+   /**
+    * If <code>globalIdsEnabled</code> is true, this map associates
+    * <code>GlobalId</code>s to active transactions.
+    */
+   private Map globalIdTx = Collections.synchronizedMap(new HashMap());
+
+
+   /**
+    * Return the ThreadInfo for the calling thread, and create if not
+    * found.
+    */
+   private ThreadInfo getThreadInfo()
+   {
+      ThreadInfo ret = (ThreadInfo) threadTx.get();
+
+      if (ret == null)
+      {
+         ret = new ThreadInfo();
+         ret.timeout = timeOut;
+         threadTx.set(ret);
+      }
+
+      return ret;
+   }
+
+   // Inner classes -------------------------------------------------
+
+   /**
+    * A simple aggregate of a thread-associated timeout value
+    * and a thread-associated transaction.
+    */
+   static class ThreadInfo
+   {
+      long timeout;
+      TransactionImpl tx;
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/XidFactory.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/XidFactory.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/XidFactory.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,163 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+import javax.transaction.xa.Xid;
+
+import org.jboss.system.ServiceMBeanSupport;
+
+/**
+ * XidFactory.java
+ * <p/>
+ * <p/>
+ * Created: Sat Jun 15 19:01:18 2002
+ *
+ * @author <a href="mailto:d_jencks at users.sourceforge.net">David Jencks</a>
+ * @jmx.mbean
+ */
+public class XidFactory
+      extends ServiceMBeanSupport
+      implements XidFactoryMBean
+{
+   
+   //default object name of the naming service
+   private static final javax.management.ObjectName NAMING_OBJECT_NAME = 
+      org.jboss.mx.util.ObjectNameFactory.create("jboss:service=Naming");
+
+   private XidFactoryImpl factory = new XidFactoryImpl();
+
+   protected void startService()
+         throws Exception
+   {
+      long jndiPortOrStartUpTime;
+      try
+      {
+         Integer jndiPort =
+            (Integer) getServer().getAttribute(NAMING_OBJECT_NAME, "Port");
+         jndiPortOrStartUpTime = jndiPort.intValue();
+      }
+      catch (Exception e)
+      {
+         jndiPortOrStartUpTime = System.currentTimeMillis();
+      }
+      factory.setUniqueInstanceId(jndiPortOrStartUpTime);
+      factory.start();
+   }
+
+   /**
+    * mbean get-set pair for field instance
+    * Get the value of instance
+    *
+    * @return value of instance
+    * @jmx:managed-attribute
+    */
+   public XidFactoryMBean getInstance()
+   {
+      return this;
+   }
+
+   public String getBaseGlobalId()
+   {
+      return factory.getBaseGlobalId();
+   }
+
+   public void setBaseGlobalId(String baseGlobalId)
+   {
+      factory.setBaseGlobalId(baseGlobalId);
+   }
+
+   public long getGlobalIdNumber()
+   {
+      return factory.getGlobalIdNumber();
+   }
+
+   public void setGlobalIdNumber(long globalIdNumber)
+   {
+      factory.setGlobalIdNumber(globalIdNumber);
+   }
+
+   public String getBranchQualifier()
+   {
+      return factory.getBranchQualifier();
+   }
+
+   public void setBranchQualifier(String branchQualifier)
+   {
+      factory.setBranchQualifier(branchQualifier);
+   }
+
+   public boolean isPad()
+   {
+      return factory.isPad();
+   }
+
+   public void setPad(boolean pad)
+   {
+      factory.setPad(pad);
+   }
+
+   public XidImpl newXid()
+   {
+      return factory.newXid();
+   }
+
+   public XidImpl newBranch(GlobalId globalId)
+   {
+      return factory.newBranch(globalId);
+   }
+
+   public XidImpl newBranch(XidImpl xid,long branchIdNum)
+   {
+      return factory.newBranch(xid, branchIdNum);
+   }
+
+   public XidImpl recreateXid(long localId)
+   {
+      return factory.recreateXid(localId);
+   }
+   
+   public XidImpl recreateXid(long localId, GlobalId globalId)
+   {
+      return factory.recreateXid(localId, globalId);
+   }
+
+   public byte[] localIdToGlobalId(long localId)
+   {
+      return factory.localIdToGlobalId(localId);
+   }
+   
+   public long extractLocalIdFrom(byte[] globalId)
+   {
+      return factory.extractLocalIdFrom(globalId);
+   }
+
+   public String getBaseBranchQualifier(byte[] branchQualifier)
+   {
+      return factory.getBaseBranchQualifier(branchQualifier);
+   }
+
+   public String toString(Xid xid)
+   {
+      return factory.toString(xid);
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/XidFactoryBase.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/XidFactoryBase.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/XidFactoryBase.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,146 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: wburke
+ * Date: May 3, 2005
+ * Time: 2:48:26 PM
+ * To change this template use File | Settings | File Templates.
+ */
+public interface XidFactoryBase
+{
+   /**
+    * mbean get-set pair for field BaseGlobalId Get the value of BaseGlobalId
+    * @return value of BaseGlobalId
+    */
+   String getBaseGlobalId() ;
+
+   /**
+    * Set the value of BaseGlobalId
+    * @param BaseGlobalId Value to assign to BaseGlobalId
+    */
+   void setBaseGlobalId(String baseGlobalId) ;
+
+   /**
+    * mbean get-set pair for field globalIdNumber Get the value of globalIdNumber
+    * @return value of globalIdNumber
+    */
+   long getGlobalIdNumber() ;
+
+   /**
+    * Set the value of globalIdNumber
+    * @param globalIdNumber Value to assign to globalIdNumber
+    */
+   void setGlobalIdNumber(long globalIdNumber) ;
+
+   /**
+    * mbean get-set pair for field branchQualifier 
+    * Get the value of BranchQualifier
+    * @return value of BranchQualifier
+    */
+   String getBranchQualifier() ;
+
+   /**
+    * Set the value of BranchQualifier
+    * @param branchQualifier Value to assign to BranchQualifier
+    */
+   void setBranchQualifier(String branchQualifier) ;
+
+   /**
+    * mbean get-set pair for field pad Get the value of pad
+    * @return value of pad
+    */
+   boolean isPad() ;
+
+   /**
+    * Set the value of pad
+    * @param pad Value to assign to pad
+    */
+   void setPad(boolean pad) ;
+
+   /**
+    * Creates a <code>XidImpl</code> for a new transaction.
+    * @return a <code>XidImpl</code> value
+    */
+   XidImpl newXid() ;
+
+   /**
+    * Creates a <code>XidImpl</code> for a branch of an existing transaction.
+    * @param globalId the code>GlobalId</code> of the existing transaction
+    * @return a <code>XidImpl</code> tor the new transaction branch.
+   */
+   XidImpl newBranch(GlobalId globalId);
+
+   /**
+    * Creates a <code>XidImpl</code> for a "fake branch" of an existing 
+    * transaction. This method exists for a single reason: some 
+    * <code>XAResource</code> implementations return false on all calls to 
+    * <code>isSameRM</code>. If <code>isSameRM</code> worked properly, then
+    * there  would be no need to generate a "fake branch" Xid for each 
+    * <code>XAResource</code> that may or may not represent a new RM. A same
+    * <code>Xid</code> (the real <code>Xid</code> for the transaction branch) 
+    * could (and should) be used for all <code>XAResource</code>s that 
+    * represent different RMs. If <code>isSameRM</code> may return false 
+    * negatives, however, the real branch Xid may be passed more than once 
+    * to a same RM in calls to <code>XAResource.start</code>. This would result
+    * in <code>XAException</code>s with error code <code>XAER_DUPID</code>.
+    * We avoid this problem by generating a "fake branch" <code>Xid</code> 
+    * for each <code>XAResource</code> that corresponds to a "new" RM according
+    * to <code>isSameRM</code>, but might actually not represent a new one.
+    *
+    * @param xid         a <code>XidImpl</code> for the existing transaction
+    * @param branchNum   a branch number to be included in the branch qualifier. 
+    * @return            a <code>XidImpl</code> for the new transaction branch.
+    */
+   XidImpl newBranch(XidImpl xid,long branchIdNum);
+   
+   XidImpl recreateXid(long localId);
+   
+   XidImpl recreateXid(long localId, GlobalId globalId);
+
+   /**
+    * Converts a local id into a global transaction id.
+    * 
+    * @param localId the local transaction id
+    * @return the global transaction id
+    */
+   public byte[] localIdToGlobalId(long localId);
+   
+   /**
+    * Extracts the local id contained in a global id.
+    * @param globalId a global id
+    * @return the local id extracted from the global id
+    */
+   long extractLocalIdFrom(byte[] globalId) ;
+
+   String getBaseBranchQualifier(byte[] branchQualifier);
+   
+   /**
+    * Describe <code>toString</code> method here.
+    * @param xid a <code>Xid</code> value
+    * @return a <code>String</code> value
+    */
+   String toString(javax.transaction.xa.Xid xid) ;
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/XidFactoryImpl.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/XidFactoryImpl.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/XidFactoryImpl.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,405 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+import javax.transaction.xa.Xid;
+
+import org.jboss.system.server.ServerConfigUtil;
+
+/**
+ * XidFactory.java
+ * <p/>
+ * <p/>
+ * Created: Sat Jun 15 19:01:18 2002
+ *
+ * @author <a href="mailto:d_jencks at users.sourceforge.net">David Jencks</a>
+ * @jmx.mbean
+ */
+public class XidFactoryImpl
+      implements XidFactoryBase
+{
+   
+   /**
+    * The default value of baseGlobalId is the host name, followed by a 
+    * colon (":"), the server's JNDI port, and a slash. It the server's JNDI 
+    * port cannot be obtained, then the startService time is used instead.
+    * <p/>
+    * This is used for building globally unique transaction identifiers.
+    * It would be safer to use the IP address, but a host name is better
+    * for humans to read and will do for now.
+    */
+   private String baseGlobalId;
+
+   /**
+    * The next transaction id to use on this host.
+    */
+   private long globalIdNumber = 0;
+
+   /**
+    * The variable <code>pad</code> says whether the byte[] should be their
+    * maximum 64 byte length or the minimum.
+    * The max length is required for Oracle..
+    */
+   private boolean pad = false;
+
+   /**
+    * The <code>branchQualifier</code> is the host name, followed by a 
+    * colon (":") and the server's JNDI port. It the server's JNDI 
+    * port cannot be obtained, then the startService time is used instead.
+    */
+   private String branchQualifier;
+
+   /**
+    * This field stores the byte reprsentation of baseGlobalId
+    * to avoid the expensive getBytes() call on that String object
+    * when we create a new Xid, which we do VERY often!
+    */
+   private byte[] baseGlobalIdBytes;
+
+   /**
+    * This field stores the byte reprsentation of branchQualifier
+    * to avoid the expensive getBytes() call on that String object.
+    */
+   private byte[] branchQualifierBytes;
+
+   /**
+    * This field stores the byte reprsentation of base branchQualifier
+    * to avoid the expensive getBytes() call on that String object.
+    */
+   private byte[] baseBranchQualifierBytes;
+
+   private long uniqueInstanceId = System.currentTimeMillis();
+
+   public void start()
+   {
+      String hostName = ServerConfigUtil.getSpecificBindAddress(); 
+
+      baseGlobalId = hostName + ":" + uniqueInstanceId;
+      // Ensure room for 14 digits of serial no and timestamp
+      if (baseGlobalId.length() > Xid.MAXGTRIDSIZE - 15)
+         baseGlobalId = baseGlobalId.substring(0, Xid.MAXGTRIDSIZE - 15);
+      baseGlobalId = baseGlobalId + "/";
+      baseGlobalIdBytes = baseGlobalId.getBytes();
+
+      branchQualifier = hostName + ":" + uniqueInstanceId;
+      int len = pad ? Xid.MAXBQUALSIZE : branchQualifier.length();
+      branchQualifierBytes = new byte[len];
+      // this method is deprecated, but does exactly what we need in a very fast
+      // way the default conversion from String.getBytes() is way too expensive
+      branchQualifier.getBytes(0, branchQualifier.length(), 
+                               branchQualifierBytes, 0);
+      
+      String baseBranchQualifier = branchQualifier + "/";
+      baseBranchQualifierBytes = new byte[baseBranchQualifier.length()];
+      // use deprecated method again
+      baseBranchQualifier.getBytes(0, baseBranchQualifier.length(), 
+                                   baseBranchQualifierBytes, 0);
+
+   }
+
+   public long getUniqueInstanceId()
+   {
+      return uniqueInstanceId;
+   }
+
+   public void setUniqueInstanceId(long uniqueInstanceId)
+   {
+      this.uniqueInstanceId = uniqueInstanceId;
+   }
+
+   /**
+    * mbean get-set pair for field BaseGlobalId
+    * Get the value of BaseGlobalId
+    *
+    * @return value of BaseGlobalId
+    * @jmx:managed-attribute
+    */
+   public String getBaseGlobalId()
+   {
+      return baseGlobalId;
+   }
+
+   /**
+    * Set the value of BaseGlobalId
+    *
+    * @param BaseGlobalId Value to assign to BaseGlobalId
+    * @jmx:managed-attribute
+    */
+   public void setBaseGlobalId(final String baseGlobalId)
+   {
+      this.baseGlobalId = baseGlobalId;
+      baseGlobalIdBytes = baseGlobalId.getBytes();
+   }
+
+   /**
+    * mbean get-set pair for field globalIdNumber
+    * Get the value of globalIdNumber
+    *
+    * @return value of globalIdNumber
+    * @jmx:managed-attribute
+    */
+   public synchronized long getGlobalIdNumber()
+   {
+      return globalIdNumber;
+   }
+
+   /**
+    * Set the value of globalIdNumber
+    *
+    * @param globalIdNumber Value to assign to globalIdNumber
+    * @jmx:managed-attribute
+    */
+   public synchronized void setGlobalIdNumber(final long globalIdNumber)
+   {
+      this.globalIdNumber = globalIdNumber;
+   }
+
+   /**
+    * mbean get-set pair for field BranchQualifier
+    * Get the value of BranchQualifier
+    *
+    * @return value of BranchQualifier
+    * @jmx:managed-attribute
+    */
+   public String getBranchQualifier()
+   {
+      return branchQualifier;
+   }
+
+   /**
+    * Set the value of BranchQualifier
+    *
+    * @param branchQualifier Value to assign to BranchQualifier
+    * @jmx:managed-attribute
+    */
+   public void setBranchQualifier(final String branchQualifier)
+   {
+      this.branchQualifier = branchQualifier;
+      
+      int len = pad ? Xid.MAXBQUALSIZE : branchQualifier.length();
+      branchQualifierBytes = new byte[len];
+      // This method is deprecated, but does exactly what we need in a 
+      // very fast way. The default conversion from String.getBytes() 
+      // is way too expensive.
+      branchQualifier.getBytes(0, branchQualifier.length(), 
+                               branchQualifierBytes, 0);
+
+      String baseBranchQualifier = branchQualifier + "/";
+      baseBranchQualifierBytes = new byte[baseBranchQualifier.length()];
+      // use deprecated method again
+      baseBranchQualifier.getBytes(0, baseBranchQualifier.length(), 
+                                   baseBranchQualifierBytes, 0);
+   }
+
+   /**
+    * mbean get-set pair for field pad
+    * Get the value of pad
+    *
+    * @return value of pad
+    * @jmx:managed-attribute
+    */
+   public boolean isPad()
+   {
+      return pad;
+   }
+
+   /**
+    * Set the value of pad
+    *
+    * @param pad Value to assign to pad
+    * @jmx:managed-attribute
+    */
+   public void setPad(boolean pad)
+   {
+      if (this.pad != pad)
+      {
+         this.pad = pad;
+         if (branchQualifier != null)
+         {
+            // update branchQualifierBytes according to the new value of pad  
+            int len = pad ? Xid.MAXBQUALSIZE : branchQualifier.length();
+            branchQualifierBytes = new byte[len];
+            // This method is deprecated, but does exactly what we need in a 
+            // very fast way. The default conversion from String.getBytes() 
+            // is way too expensive.
+            branchQualifier.getBytes(0, branchQualifier.length(), 
+                                     branchQualifierBytes, 0);
+         }
+      }
+            
+   }
+
+   /**
+    * Creates a <code>XidImpl</code> for a new transaction.
+    *
+    * @return a <code>XidImpl</code> value
+    * @jmx.managed-operation
+    */
+   public XidImpl newXid()
+   {
+      long localId = getNextId();
+      return new XidImpl(localIdToGlobalId(localId), 
+                         branchQualifierBytes, 
+                         (int) localId, 
+                         localId);
+   }
+
+   /**
+    * Creates a <code>XidImpl</code> for a branch of an existing transaction.
+    *
+    * @param globalId the code>GlobalId</code> of the existing transaction
+    * @return a <code>XidImpl</code> tor the new transaction branch.
+    * @jmx.managed-operation
+    */
+   public XidImpl newBranch(GlobalId globalId)
+   {
+      long localId = getNextId();
+      return new XidImpl(globalId, branchQualifierBytes, localId);
+   }
+
+   /**
+    * Creates a <code>XidImpl</code> for a "fake branch" of an existing 
+    * transaction. This method exists for a single reason: some 
+    * <code>XAResource</code> implementations return false on all calls to 
+    * <code>isSameRM</code>. If <code>isSameRM</code> worked properly, then
+    * there  would be no need to generate a "fake branch" Xid for each 
+    * <code>XAResource</code> that may or may not represent a new RM. A same
+    * <code>Xid</code> (the real <code>Xid</code> for the transaction branch) 
+    * could (and should) be used for all <code>XAResource</code>s that 
+    * represent different RMs. If <code>isSameRM</code> may return false 
+    * negatives, however, the real branch Xid may be passed more than once 
+    * to a same RM in calls to <code>XAResource.start</code>. This would result
+    * in <code>XAException</code>s with error code <code>XAER_DUPID</code>.
+    * We avoid this problem by generating a "fake branch" <code>Xid</code> 
+    * for each <code>XAResource</code> that corresponds to a "new" RM according
+    * to <code>isSameRM</code>, but might actually not represent a new one.
+    *
+    * @param xid         a <code>XidImpl</code> for the existing transaction
+    * @param branchNum   a branch number to be included in the branch qualifier. 
+    * @return            a <code>XidImpl</code> for the new transaction branch.
+    * @jmx.managed-operation
+    */
+   public XidImpl newBranch(XidImpl xid, long branchNum)
+   {
+      String id = Long.toString(branchNum);
+      int len = pad ? Xid.MAXBQUALSIZE 
+                    : baseBranchQualifierBytes.length + id.length(); 
+      byte[] branchQualifier = new byte[len];
+      System.arraycopy(baseBranchQualifierBytes, 0, 
+                       branchQualifier, 0, 
+                       baseBranchQualifierBytes.length);
+      // This method is deprecated, but does exactly what we need in a very 
+      // fast way. The conversion via String.getBytes() is way too expensive.
+      id.getBytes(0, id.length(), branchQualifier, 
+                  baseBranchQualifierBytes.length);
+      return new XidImpl(xid, branchQualifier);
+   }
+
+   public XidImpl recreateXid(long localId)
+   {
+      return new XidImpl(localIdToGlobalId(localId), 
+            branchQualifierBytes, 
+            (int) localId, 
+            localId);
+   }
+   
+   public XidImpl recreateXid(long localId, GlobalId globalId)
+   {
+      return new XidImpl(globalId, branchQualifierBytes, localId);
+   }
+
+   /**
+    * Converts a local id into a global transaction id.
+    * 
+    * @param localId the local transaction id
+    * @return the global transaction id
+    */
+   public byte[] localIdToGlobalId(long localId)
+   {
+      String id = Long.toString(localId);
+      int len = pad ? Xid.MAXGTRIDSIZE : id.length() + baseGlobalIdBytes.length;
+      byte[] globalId = new byte[len];
+      System.arraycopy(baseGlobalIdBytes, 0, globalId, 0, baseGlobalIdBytes.length);
+      // this method is deprecated, but does exactly what we need in a very fast way
+      // the default conversion from String.getBytes() is way too expensive
+      id.getBytes(0, id.length(), globalId, baseGlobalIdBytes.length);
+      return globalId;
+   }
+
+   /**
+    * Extracts the local id contained in a global id.
+    *
+    * @param globalId a global id
+    * @return the local id extracted from the global id
+    * @jmx:managed-operation
+    */
+   public long extractLocalIdFrom(byte[] globalId)
+   {
+      int i, start;
+      int len = globalId.length;
+
+      for (i = 0; globalId[i++] != (byte) '/';)
+         ;
+      start = i;
+      while (i < len && globalId[i] != 0)
+         i++;
+      String globalIdNumber = new String(globalId, 0, start, i - start);
+      return Long.parseLong(globalIdNumber);
+   }
+
+   /**
+    * @param branchQualifier
+    * @return
+    * @jmx:managed-operation
+    */
+   public String getBaseBranchQualifier(byte[] branchQualifier)
+   {
+      int len = branchQualifier.length;
+      int i = 0;
+      
+      while (branchQualifier[i] != (byte) '/' 
+               && branchQualifier[i] != (byte) 0
+               && i < len)
+         i++;
+
+      String base = new String(branchQualifier, 0, 0, i);
+      return base;
+   }
+
+   /**
+    * Describe <code>toString</code> method here.
+    *
+    * @param xid a <code>Xid</code> value
+    * @return a <code>String</code> value
+    * @jmx.managed-operation
+    */
+   public String toString(Xid xid)
+   {
+      return XidImpl.toString(xid);
+   }
+
+   private synchronized long getNextId()
+   {
+      return ++globalIdNumber;
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/XidFactoryMBean.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/XidFactoryMBean.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/XidFactoryMBean.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,35 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+/**
+ * MBean interface.
+ */
+public interface XidFactoryMBean extends org.jboss.system.ServiceMBean, XidFactoryBase {
+
+   /**
+    * mbean get-set pair for field instance Get the value of instance
+    * @return value of instance
+    */
+   XidFactoryMBean getInstance() ;
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/XidImpl.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/XidImpl.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/XidImpl.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,351 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm;
+
+import javax.transaction.xa.Xid;
+
+/**
+ *  This object encapsulates the ID of a transaction.
+ *  This implementation is immutable and always serializable at runtime.
+ *
+ *  @see TransactionImpl
+ *  @author <a href="mailto:rickard.oberg at telkel.com">Rickard ???berg</a>
+ *  @author <a href="mailto:osh at sparre.dk">Ole Husgaard</a>
+ *  @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ *  @version $Revision: 37459 $
+ */
+public class XidImpl
+   implements Xid, java.io.Serializable
+{
+   // Constants -----------------------------------------------------
+
+   static final long serialVersionUID = -4175838107150528488L;
+
+   public static final int JBOSS_FORMAT_ID = 0x0101;
+
+   // Static variable -----------------------------------------------
+
+   private static boolean trulyGlobalIdsEnabled = false;
+
+   // Attributes ----------------------------------------------------
+
+   /**
+    *  Format id of this instance. 
+    *  A JBoss-generated Xids has JBOSS_FORMAT_ID in this field.
+    */
+   private final int formatId;
+
+   /**
+    *  Global transaction id of this instance.
+    *  The coding of this class depends on the fact that this variable is
+    *  initialized in the constructor and never modified. References to
+    *  this array are never given away, instead a clone is delivered.
+    */
+   private final byte[] globalId;
+
+   /**
+    *  Branch qualifier of this instance.
+    *  This identifies the branch of a transaction.
+    */
+   private final byte[] branchId;
+
+   /**
+    *  Hash code of this instance. This is really a sequence number.
+    */
+   private final int hash;
+
+   /**
+    *  Local id of this instance. This field uniquely identifies a
+    *  transaction within a given JBoss server.
+    */
+   private final long localId;
+
+   /**
+    *  Global id of this instance. This field uniquely identifies a
+    *  transaction in a distributed environment.
+    */
+   private final GlobalId trulyGlobalId;
+
+
+   // Static --------------------------------------------------------
+
+   /**
+    *  Setter for class variable trulyGlobalIdsEnabled.
+    */
+   public static void setTrulyGlobalIdsEnabled(boolean newValue) 
+   {
+      trulyGlobalIdsEnabled = newValue;
+   }
+
+   /**
+    *  Getter for class variable trulyGlobalIdsEnabled.
+    */
+   public static boolean getTrulyGlobalIdsEnabled() 
+   {
+      return trulyGlobalIdsEnabled;
+   }
+
+   /**
+    *  Return a string that describes any Xid instance.
+    */
+   public static String toString(Xid id) 
+   {
+      if (id == null)
+         return "[NULL Xid]";
+
+      String s = id.getClass().getName();
+      s = s.substring(s.lastIndexOf('.') + 1);
+      s = s + "[FormatId=" + id.getFormatId()
+            + ", GlobalId=" + new String(id.getGlobalTransactionId()).trim()
+            + ", BranchQual=" + new String(id.getBranchQualifier()).trim()
+            + ((id instanceof XidImpl) 
+                  ? ", localId=" + LocalId.toString(((XidImpl)id).localId ) 
+                  : "") 
+            + "]";
+
+      return s;
+   }
+
+   // Constructors --------------------------------------------------
+
+   /**
+    *  Create a new instance.
+    */
+   public XidImpl(int formatId, 
+                  byte[] globalId, byte[] branchId, int hash, long localId)
+   {
+      this.formatId = formatId;
+      this.globalId = globalId;
+      this.branchId = branchId;
+      this.hash = hash;
+      this.localId = localId;
+      this.trulyGlobalId = (trulyGlobalIdsEnabled) 
+                           ? new GlobalId(formatId, globalId) 
+                           : null;
+   }
+
+   /**
+    *  Create a new instance with JBOSS_FORMAT_ID.
+    */
+   XidImpl(byte[] globalId, byte[] branchId, int hash, long localId)
+   {
+      this.formatId = JBOSS_FORMAT_ID;
+      this.globalId = globalId;
+      this.branchId = branchId;
+      this.hash = hash;
+      this.localId = localId;
+      this.trulyGlobalId = (trulyGlobalIdsEnabled) 
+                           ? new GlobalId(JBOSS_FORMAT_ID, globalId, hash)
+                           : null;
+   }
+
+   /**
+    *  Create a new branch of an existing global transaction ID.
+    *
+    *  @param xidImpl The transaction ID to create a new branch of.
+    *  @param branchId The ID of the new branch.
+    *
+    */
+   public XidImpl(final XidImpl xidImpl, final byte[] branchId)
+   {
+      this.formatId = xidImpl.formatId;
+      this.globalId = xidImpl.globalId; // reuse array, we never modify it
+      this.branchId = branchId;
+      this.hash = xidImpl.hash;
+      this.localId = xidImpl.localId;
+      this.trulyGlobalId = (trulyGlobalIdsEnabled) 
+                           ? xidImpl.trulyGlobalId 
+                           : null;
+   }
+
+   /**
+    *  Create a new branch of an existing global transaction.
+    *
+    *  @param globalId identifies the transaction to create a new branch of
+    *  @param branchId the id that distiguishes the new branch from other
+    *                  branches of the same transaction
+    *  @param localId  the local id of the transaction 
+    *
+    */
+   public XidImpl(final GlobalId globalId, final byte[] branchId, long localId)
+   {
+      this.formatId = globalId.getFormatId();
+      this.globalId = globalId.getGlobalTransactionId();
+      this.branchId = branchId;
+      this.hash = globalId.hashCode();
+      this.localId = localId;
+      this.trulyGlobalId = (trulyGlobalIdsEnabled) 
+                           ? globalId 
+                           : null;
+   }
+
+   // Public --------------------------------------------------------
+
+   // Xid implementation --------------------------------------------
+
+   /**
+    *  Return the global transaction id of this transaction.
+    */
+   public byte[] getGlobalTransactionId()
+   {
+      return (byte[])globalId.clone();
+   }
+
+   /**
+    *  Return the branch qualifier of this transaction.
+    */
+   public byte[] getBranchQualifier()
+   {
+      if (branchId.length == 0)
+         return branchId; // Zero length arrays are immutable.
+      else
+         return (byte[])branchId.clone();
+   }
+
+   /**
+    *  Return the format identifier of this transaction.
+    *
+    *  The format identifier augments the global id and specifies
+    *  how the global id and branch qualifier should be interpreted.
+    */
+   public int getFormatId() 
+   {
+      // The id we return here should be different from all other transaction
+      // implementations.
+      // Known IDs are:
+      // -1:     Sometimes used to denote a null transaction id.
+      // 0:      OSI TP (javadoc states OSI CCR, but that is a bit misleading
+      //         as OSI CCR doesn't even have ACID properties. But OSI CCR and
+      //         OSI TP do have the same id format.)
+      // 1:      Was used by early betas of jBoss.
+      // 0x0101: The JBOSS_FORMAT_ID we use here.
+      // 0xBB14: Used by JONAS.
+      // 0xBB20: Used by JONAS.
+
+      return formatId;
+   }
+
+   /**
+    *  Compare for equality.
+    *
+    *  Instances are considered equal if they are both instances of XidImpl,
+    *  and if they have the same format id, the same global transaction id 
+    *  and the same transaction branch qualifier.
+    */
+   public boolean equals(Object obj)
+   {
+      if(obj==this) 
+         return true;
+      if (obj instanceof XidImpl) {
+         XidImpl other = (XidImpl)obj;
+
+         if (formatId != other.formatId ||
+             globalId.length != other.globalId.length ||
+             branchId.length != other.branchId.length)
+            return false;
+
+         for (int i = 0; i < globalId.length; ++i)
+            if (globalId[i] != other.globalId[i])
+               return false;
+
+         for (int i = 0; i < branchId.length; ++i)
+            if (branchId[i] != other.branchId[i])
+               return false;
+
+         return true;
+      }
+      return false;
+   }
+
+   public int hashCode()
+   {
+      return hash;
+   }
+
+   public String toString()
+   {
+      return toString(this);
+   }
+
+   // Methods specific to JBoss Xid implementation ------------------
+
+   /**
+    *  Return the local id that identifies this transaction 
+    *  within the JBoss server.
+    */
+   public long getLocalIdValue() 
+   {
+      return localId;
+   }
+
+   /**
+    *  Return a LocalId instance that identifies this transaction 
+    *  within the JBoss server.
+    */
+   public LocalId getLocalId() 
+   {
+      return new LocalId(localId);
+   }
+
+   /**
+    *  Return a GlobalId instance that identifies this transaction 
+    *  in a distributed environment.
+    */
+   public GlobalId getTrulyGlobalId() 
+   {
+      return trulyGlobalId;
+   }
+
+   /**
+    *  Compare for same transaction.
+    *
+    *  Instances represent the same transaction if they have the same 
+    *  format id and global transaction id.
+    */
+   public boolean sameTransaction(XidImpl other)
+   {
+      if(other == this) 
+         return true;
+      if (formatId != other.formatId ||
+          globalId.length != other.globalId.length)
+         return false;
+
+      for (int i = 0; i < globalId.length; ++i)
+         if (globalId[i] != other.globalId[i])
+            return false;
+
+      return true;
+   }
+
+   /**
+    *  Return the global transaction id of this transaction.
+    *  Unlike the {@link #getGlobalTransactionId()} method, this one
+    *  returns a reference to the global id byte array that may <em>not</em>
+    *  be changed.
+    */
+   public byte[] getInternalGlobalTransactionId()
+   {
+      return globalId;
+   }
+
+}
+

Added: trunk/transaction/src/main/org/jboss/tm/integrity/AbstractTransactionIntegrity.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/integrity/AbstractTransactionIntegrity.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/integrity/AbstractTransactionIntegrity.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,63 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.integrity;
+
+import javax.transaction.Transaction;
+
+import org.jboss.logging.Logger;
+import org.jboss.tm.TransactionImpl;
+
+/**
+ * A NOOP implementation of transaction integrity.<p>
+ * 
+ * Implementations should extend this for future compatibility.
+ * 
+ * @author <a href="adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision: 37459 $
+ */
+public class AbstractTransactionIntegrity implements TransactionIntegrity
+{
+   /** The log */
+   protected Logger log = Logger.getLogger(getClass());
+   
+   public void checkTransactionIntegrity(TransactionImpl transaction)
+   {
+      // Do nothing
+   }
+   
+   /**
+    * Mark the transaction for rollback
+    * 
+    * @param transaction the transacton
+    */
+   protected void markRollback(Transaction transaction)
+   {
+      try
+      {
+         transaction.setRollbackOnly();
+      }
+      catch (Exception e)
+      {
+         log.warn("Unable to mark the transaction for rollback " + transaction, e);
+      }
+   }
+}

Added: trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransaction.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransaction.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransaction.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,40 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.integrity;
+
+import org.jboss.system.ServiceMBeanSupport;
+
+/**
+ * An MBean to configure the FailIncomplete policy.
+ * 
+ * @author <a href="adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision: 37459 $
+ */
+public class FailIncompleteTransaction extends ServiceMBeanSupport 
+   implements FailIncompleteTransactionMBean
+{
+   public TransactionIntegrity createTransactionIntegrity()
+   {
+      return new FailIncompleteTransactionIntegrity();
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionIntegrity.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionIntegrity.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionIntegrity.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,61 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.integrity;
+
+import java.util.Set;
+
+import org.jboss.tm.TransactionImpl;
+
+/**
+ * A transaction integrity that rolls back the transaction
+ * if there are other threads associated with it.
+ * 
+ * @author <a href="adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision: 37459 $
+ */
+public class FailIncompleteTransactionIntegrity extends AbstractTransactionIntegrity
+{
+   public void checkTransactionIntegrity(TransactionImpl transaction)
+   {
+      // Assert the only thread is ourselves
+      Set threads = transaction.getAssociatedThreads();
+      String rollbackError = null;
+      synchronized (threads)
+      {
+         if (threads.size() > 1)
+            rollbackError = "Too many threads " + threads + " associated with transaction " + transaction;
+         else if (threads.size() != 0)
+         {
+            Thread other = (Thread) threads.iterator().next();
+            Thread current = Thread.currentThread();
+            if (current.equals(other) == false)
+               rollbackError = "Attempt to commit transaction " + transaction + " on thread " + current +
+                          " with other threads still associated with the transaction " + other;
+         }
+      }
+      if (rollbackError != null)
+      {
+         log.error(rollbackError, new IllegalStateException("STACKTRACE"));
+         markRollback(transaction);
+      }
+   }
+}

Added: trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionMBean.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionMBean.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/integrity/FailIncompleteTransactionMBean.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,29 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.integrity;
+
+import org.jboss.system.ServiceMBean;
+
+public interface FailIncompleteTransactionMBean extends ServiceMBean, TransactionIntegrityFactory
+{
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrity.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrity.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrity.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,51 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.integrity;
+
+import org.jboss.tm.TransactionImpl;
+
+/**
+ * A policy that checks a transaction before allowing a commit
+ *
+ * @author <a href="adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision: 37459 $
+ */
+public interface TransactionIntegrity
+{
+   /**
+    * Checks whether a transaction can be committed.<p>
+    * 
+    * The policy is allowed to wait, e.g. if there
+    * are other threads still associated with the transaction.<p>
+    * 
+    * This method is invoked before any transaction synchronizations'
+    * beforeCompletions.<p>
+    *
+    * This policy should not invoke any methods that change the
+    * state of the transaction other than <code>setRollbackOnly()</code>
+    * to force a rollback or registering a transaction synchronization.
+    * 
+    * @param transaction the transaction
+    * @throws SecurityException if a commit is not allowed from this context
+    */
+   void checkTransactionIntegrity(TransactionImpl transaction);
+}

Added: trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrityFactory.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrityFactory.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/integrity/TransactionIntegrityFactory.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,38 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.integrity;
+
+/**
+ * A transaction integrity factory
+ *
+ * @author <a href="adrian at jboss.com">Adrian Brock</a>
+ * @version $Revision: 37459 $
+ */
+public interface TransactionIntegrityFactory
+{
+   /**
+    * Create a new transaction integrity policy
+    * 
+    * @return the transaction integrity policy
+    */
+   TransactionIntegrity createTransactionIntegrity();
+}

Added: trunk/transaction/src/main/org/jboss/tm/package.html
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/package.html	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/package.html	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,152 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+  <head>
+    <!--
+ JBoss, Home of Professional Open Source
+ Copyright 2005, JBoss Inc., and individual contributors as indicated
+ by the @authors tag. See the copyright.txt in the distribution for a
+ full listing of individual contributors.
+
+ This is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as
+ published by the Free Software Foundation; either version 2.1 of
+ the License, or (at your option) any later version.
+
+ This software is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this software; if not, write to the Free
+ Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+    -->
+    <!-- $Id: package.html 38414 2005-11-23 07:55:45Z adrian $ -->
+  </head>
+
+  <body bgcolor="white">
+    <p><em>The default Transaction Manager implementation</em>.
+
+
+    <h2>Package Specification</h2>
+    <ul>
+
+The classes in this package belong may be grouped into two:
+<ul>
+  <li><em>Application server interface</em>. This group consists of the
+     two classes {@link: TransactionPropagationContextFactory} and
+     {@link: TransactionPropagationContextImporter}. Together these two
+     classes form the JBoss-specific Communication Resource Manager (CRM).
+  </li>
+  <li><em>The default Transaction Service</em>. This is the rest of the
+     classes in this package. It is a very fast JTA implementation with
+     two drawbacks:
+     <ul>
+       <li>
+         It has no transactional logging or recovery after server crashes.
+       </li>
+       <li>
+         Transactions cannot be propagated from one server VM to another.
+         But the thin-client UserTransaction service works fine, in case
+         you just want to control transactions on a single server from your
+         stand-alone clients.
+       </li>
+     </ul>
+  </li>
+       
+</ul>
+    </ul>
+      
+    <h2>The JBoss-specific Communication Resource Manager</h2>
+    <p>A CRM is responsible for converting local transactions from/to a
+    form that can be serialized so it can be passed over the wire to
+    another Java VM.</p>
+    <p>The JTA specification does not define the CRM, so we have implemented
+    our own.</p>
+    <p>The J2EE specification suggests that the CRM used is the OTS
+    CosTSPortability CRM, but that depends heavily on CORBA, and JBoss is
+    not dependent on any particular object request broker technology, so
+    we do not use that.</p>
+    <p>The advantage of using our own CRM implementation instead of
+    CosTSPortablilty is twofold:
+    <ul>
+      <li>
+        JBoss does not depend on CORBA, and will run just fine without it.
+      </li>
+      <li>
+        JBoss is able to use JTA implementations that are completely
+        incompatible with OTS.
+      </li>
+    </ul>
+
+    <h2>Adapting an existing JTA implementation for JBoss</h2>
+    <p>In JBoss everything is a MBean. Implementing your JTA implementation
+    as an MBean makes it simpler to manage it in a standardized way.</p>
+    Since your JTA implementation is a service you probably want to make
+    your MBean implemementation extend from
+    <code>org.jboss.system.ServiceMBeanSupport</code>.</p>
+    When running in the server, your MBean should publish three objects
+    in JNDI:
+    <ul>
+      <li>
+        <em>java:/TransactionManager</em>
+        When this JNDI name is looked up, it should return an object instance
+        that implements <code>javax.transaction.TransactionManager</code>.
+        This is the standard JTA transaction manager that the JBoss server
+        uses to control transactions.
+      </li>
+      <li>
+        <em>java:/TransactionPropagationContextImporter</em>
+        When this JNDI name is looked up, it should return an object instance
+        that implements
+        <code>org.jboss.tm.TransactionPropagationContextImporter</code>.
+        This is used by the JBoss communication layers to convert the wire
+        representation of a transaction to a
+        <code>javax.transaction.Transaction</code> instance that can be used
+        locally.
+      </li>
+      <li>
+        <em>java:/TransactionPropagationContextFactory</em>
+        When this JNDI name is looked up, it should return an object instance
+        that implements
+        <code>org.jboss.tm.TransactionPropagationContextFactory</code>.
+        This is used by the JBoss communication layers to convert the wire
+        representation of a <code>javax.transaction.Transaction</code>
+        instance to a form that can be passed over the wire to another JBoss
+        server.
+      </li>
+    </ul>
+    <p>See the implementation of
+    <code>org.jboss.tm.TransactionManagerService</code> for an example of
+    such a MBean. Please note that the three names your MBean should publish
+    in JNDI need not refer to the same object instance as the
+    <code>TransactionManagerService</code> does.</p>
+
+    <p>Please note that the transaction propagation context (TPC) used by
+    the CRM interfaces <code>TransactionPropagationContextImporter</code>
+    and <code>TransactionPropagationContextFactory</code> is of type
+    <code>java.lang.Object</code>. This allows for the widest range of
+    possible TPC implementations. For your TPC to work with the JBoss
+    JRMP transport, it should be Java-serializable at runtime.</p>
+
+    <h2>Related Documentation</h2>
+    <ul>
+      <li>
+        <a href="http://java.sun.com/products/jta/">The JTA specification</a>
+      </li>
+      <li>
+        <a href="http://www.omg.org/technology/documents/formal/transaction_service.htm">The OTS specification</a>
+      </li>
+    </ul>
+
+    <h2>Package Status</h2>
+    <ul>
+      <li><font color="green"><b>STABLE</b></font>
+    </ul>
+
+    <!-- Put @see and @since tags down here. -->
+
+  </body>
+</html>
+

Added: trunk/transaction/src/main/org/jboss/tm/recovery/BatchLog.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/BatchLog.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/BatchLog.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,413 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+import org.jboss.logging.Logger;
+
+/**
+ * Class that encapsulates a log file opened for writing. It provides 
+ * <code>write</code> methods that append log records to the log file.
+ * A <code>BatchLog</code> instance belongs to some <code>BatchWriter</code>
+ * and its write methods are called only from the <code>BatchWriter</code>
+ * thread. 
+ * <p>
+ * To avoid changes in its metadata, the log file is created with a fixed
+ * length, which should never change until the file is closed. 
+ * <p>
+ * A newly created log file is clean: it contains a header object followed
+ * by a sequence of null bytes that takes the entire lenght of the file. Log
+ * files are reusable, but they must be cleaned up for reuse. Restarting a
+ * <code>BatchLog</code> means overwriting with null bytes all the data that 
+ * follows the header and returning the <code>BatchLog</code> to the pool of 
+ * clean <code>BatchLog</code> instances maintained by a 
+ * <code>BatchWriter</code>.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+class BatchLog 
+   implements TxCompletionHandler
+{
+   /**
+    * Class <code>Logger</code>, for trace messages.
+    */
+   private static Logger errorLog = Logger.getLogger(BatchLog.class);
+
+   /** 
+    * Length of the null-filled buffer used to clean log file. 
+    */ 
+   private static final int CLEAN_LENGTH = 16 * 1024;
+   
+   /** 
+    * Buffer used to fill the log file with null bytes. 
+    */ 
+   private static byte[] nulls = new byte[CLEAN_LENGTH];
+   
+   /** 
+    * The log file. 
+    */
+   private File logFile;
+   
+   /**
+    *  A <code>RandomAccessFile</code> view of the log file.
+    */
+   private RandomAccessFile os;
+   
+   /**
+    * The <code>RandomAccessFile</code>'s underlying <code>FileChannel</code>.
+    */
+   private FileChannel channel;
+   
+   /**
+    * Number of transactions logged. Counts commit, multi-TM commit, prepare, 
+    * and JCA prepare records written out to the log. Only the 
+    * <code>BatchWriter</code> thread updates this field, which is declared 
+    * as volatile so that its updates are seen by any threads that call
+    * <code>handleTxCompletion</code>.
+    */
+   private volatile int numLoggedTransactions;
+   
+   /**
+    * Number of end records written out to the log. Only the 
+    * <code>BatchWriter</code> thread updates this field, which is declared 
+    * as volatile so that its updates are seen by any threads that call
+    * <code>handleTxCompletion</code>.
+    */
+   private volatile int numEndRecords;
+   
+   /**
+    * Number of completed transactions that need no end records. Access to this
+    * field is synchronized, as it is concurrently updated via calls to 
+    * <code>handleTxCompletion</code>.
+    */
+   private int numLocalTransactionsCompleted;
+   
+   /**
+    * Indicates that this <code>BatchLog</code> will not receive additional
+    * records for new transactions. This <code>BatchLog</code> should be 
+    * cleaned up and returned to the <code>BatchWriter</code>'s pool of clean
+    * <code>BatchLog</code> instances as soon as every outstanding transaction
+    * signals its completion either by writing out an end record or by invoking
+    * <code>handleTxCompletion</code>.  
+    */      
+   private boolean markedForRestart;
+   
+   /** 
+    * Header object written to the beginning of the log file via Java 
+    * serialization. It is preceded by four bytes (an int value) with the 
+    * length of the serialized header.
+    */ 
+   private Object header;
+   
+   /**
+    * Log file position that follows the four bytes with the header lenght and
+    * the header object. (Its value is headerLenght + 4.)
+    */
+   private long topFp;
+   
+   /**
+    * The <code>BatchWriter</code> that owns this <code>BatchLog</code>.
+    */
+   private BatchWriter writer;
+   
+   /**
+    * Auxiliary buffer used to fill up with null bytes the part of the file 
+    * that follows the header.
+    */
+   private ByteBuffer cleanBuffer = ByteBuffer.wrap(nulls);
+
+   /**
+    * Constructs a new <code>BatchLog</code>.
+    * 
+    * @param writer   the <code>BatchWriter</code> that will own 
+    *                 the new <code>BatchLog</code> 
+    * @param header   an object to be written at the beginning of the log file
+    * @param dir      the directory in which the log file will be created
+    * @param fileSize the fixed size of the log file
+    * @throws IOException
+    */
+   BatchLog(BatchWriter writer, Object header, File dir, int fileSize) 
+      throws IOException
+   {
+      this.writer = writer;
+      this.header = header;
+      logFile = File.createTempFile("TX_RECOVERY_LOG", ".log", dir);
+      os = new RandomAccessFile(logFile, "rw");
+
+      ByteArrayOutputStream baos = new ByteArrayOutputStream();
+      ObjectOutputStream oos = new ObjectOutputStream(baos);
+      oos.writeObject(header);
+      byte[] bytes = baos.toByteArray();
+
+      os.setLength(fileSize);
+      os.writeInt(bytes.length);
+      os.write(bytes);
+
+      channel = os.getChannel();
+      // Force also metadata. We do this just once, as the file size is fixed.
+      channel.force(true); 
+      topFp = channel.position();
+      
+      cleanUpLogFile();
+   }
+
+   /**
+    * Gets the <code>BatchWriter</code> that owns this <code>BatchLog</code>.
+    *    
+    * @return the <code>BatchWriter</code> that issues write calls on this
+    *         this <code>BatchLog</code>.   
+    */
+   BatchWriter getBatchWriter()
+   {
+      return writer;
+   }
+   
+   /**
+    * Gets this <code>BatchLog</code>'s current position, which is also the
+    * number of currently used bytes of its log file.
+    *   
+    * @return the offset from the beginning of the log file, in bytes, 
+    *         at which the next write will happen.
+    * @throws IOException
+    */
+   int getPosition()
+         throws IOException
+   {  
+      return (int) channel.position();
+   }
+
+   /**
+    * Gets the name of the underlying log file.
+    *  
+    * @return the name of this <code>BatchLog</code>'s log file. 
+    */
+   String getFilename()
+   {
+      return logFile.getName();
+   }
+
+   /**
+    * Writes one record at the current position of this <code>BatchLog</code>.
+    *  
+    * @param record      a buffer with the record to be written
+    * @param isEndRecord true if the record is an end record and false otherwise
+    * @throws IOException
+    */
+   void write(ByteBuffer record, boolean isEndRecord) 
+       throws IOException
+   {
+      channel.write(record);
+      if (isEndRecord)
+      {
+         numEndRecords++;
+         
+         synchronized (this)
+         {
+            if (markedForRestart == true && 
+                  numLoggedTransactions == 
+                     numLocalTransactionsCompleted + numEndRecords)
+            {
+               writer.restartBatchLog(this);
+            }
+         }
+      }
+      else
+      {
+         channel.force(false);
+         numLoggedTransactions++;
+      }
+   }
+
+   /**
+    * Writes a sequence of records to this <code>BatchLog</code>.
+    * The sequence of records is taken from the given array of buffers, 
+    * starting at the specified offset, and it is written at the current
+    * position of this <code>BatchLog</code>.
+    * 
+    * @param records an array of buffers containing records that should be 
+    *                written to the log
+    * @param offset  the index of the first record of the <code>records</code> 
+    *                array that should be written to the log
+    * @param length  the number of records that should be written to the log
+    * @param numTransactionRecords specifies how many of the <code>lenght</code>
+    *                records are records for new transactions (commit, multi-TM
+    *                commit, prepare and JCA prepare records). The remaining
+    *                <code>length - numTransactionRecords</code> records are
+    *                end-of-transaction records.
+    * @throws IOException
+    */
+   void write(ByteBuffer[] records, 
+              int offset, 
+              int length, 
+              int numTransactionRecords) 
+      throws IOException
+   {
+      channel.write(records, offset, length);
+
+      if (numTransactionRecords > 0)
+      {
+         channel.force(false);
+         numLoggedTransactions += numTransactionRecords;
+      }
+
+      if (numTransactionRecords < length)
+      {
+         numEndRecords += (length - numTransactionRecords);
+      
+         synchronized (this)
+         {
+            if (markedForRestart == true && 
+                  numLoggedTransactions == 
+                     numLocalTransactionsCompleted + numEndRecords)
+            {
+               writer.restartBatchLog(this);
+            }
+         }
+      }
+      
+   }
+   
+   /**
+    * Signals the end of the two-phase commit protocol for a committed 
+    * transaction that does not need an end record to be logged. This method 
+    * should be invoked when the second phase of the two-phase commit protocol 
+    * completes successfully.
+    *
+    * @param localTransactionId the local id of the completed transaction.
+    */
+   public void handleTxCompletion(long localTransactionId)
+   {
+      synchronized (this)
+      {
+         numLocalTransactionsCompleted++;
+         if (markedForRestart == true && 
+               numLoggedTransactions == 
+                  numLocalTransactionsCompleted + numEndRecords)
+         {
+            writer.restartBatchLog(this);
+         }
+      }
+   }
+
+   /**
+    * Marks this <code>BatchLog</code> for restart. The restart will occur
+    * only when there are no more outstanding transactions, i.e, when
+    * <code>numLocalTransactionsCompleted + numEndRecords</code> reaches
+    * <code>numLoggedTransactions</code>.
+    */
+   void markForRestart()
+   {
+      synchronized (this)
+      {
+         markedForRestart = true;
+         if (numLoggedTransactions == 
+            numLocalTransactionsCompleted + numEndRecords)
+         {
+            writer.restartBatchLog(this);
+         }
+      }
+   }
+
+   /**
+    * Restarts this <code>BatchLog</code>. Overwrites with null bytes all 
+    * log records in the log file, then returns the <code>BatchLog</code> 
+    * to its <code>BatchWriter</code>s pool of clean <code>BatchLog</code>
+    * instances. 
+    * <p>
+    * Only the <code>LogRestarter</code> calls this method.
+    * 
+    * @throws IOException
+    */
+   void restart()
+      throws IOException
+   {
+      // TODO may have to rewrite header when we start recording XAResource 
+      // handles in it as a new one may be deployed/undeployed
+      channel.position(topFp);
+      cleanUpLogFile();
+      writer.getNextLogs().add(this);
+   }
+   
+   /**
+    * Overwrites with null bytes all log records in the log file, without
+    * changing the size of the file. 
+    */
+   void cleanUpLogFile()
+      throws IOException
+   {
+      numLoggedTransactions = 0;
+      numLocalTransactionsCompleted = 0;
+      numEndRecords = 0;
+      markedForRestart = false;
+
+      // Overwites the remainder of the log file with null bytes.
+      cleanBuffer.limit(cleanBuffer.capacity());
+      while (channel.position() <= channel.size() - cleanBuffer.limit())
+      {
+         cleanBuffer.rewind();
+         channel.write(cleanBuffer);
+      }
+      cleanBuffer.limit((int) (channel.size() - channel.position()));         
+      cleanBuffer.rewind();
+      channel.write(cleanBuffer);
+      channel.force(false);
+      
+      channel.position(topFp);
+   }
+
+   /**
+    * Closes this <code>BatchLog</code>. If there are no outstanding
+    * transactions then all log records in the log file are erased.
+    */
+   void close()
+   {
+      errorLog.info("Closing transaction log " + getFilename() +
+                    ", numLoggedTransactions=" + numLoggedTransactions +
+                    ", numLocalTransactionsCompleted=" + 
+                    numLocalTransactionsCompleted +
+                    ", numEndRecords=" + numEndRecords);
+      try
+      {
+         if (numLoggedTransactions == 
+            numLocalTransactionsCompleted + numEndRecords)
+         {
+            channel.position(topFp);
+            channel.truncate(topFp);
+         }
+         os.close();
+      }
+      catch (IOException e)
+      {
+         errorLog.error("Error closing transaction log " + getFilename(), e);
+      }
+   }
+   
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogReader.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogReader.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogReader.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,454 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.jboss.logging.Logger;
+import org.jboss.tm.XidFactoryBase;
+
+/**
+ * Simple implementation of <code>RecoveryLogReader</code> used at recovery
+ * time. The <code>BatchRecoveryLogger</code>'s implementation of method
+ * <code>getRecoveryLogs()</code> instantiates 
+ * <code>BatchRecoveryLogReader</code>s for the existing recovery log files.
+ * It returns an array containing those readers, which the recovery manager 
+ * uses to get the information in the log files.
+ * 
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+class BatchRecoveryLogReader 
+   implements RecoveryLogReader
+
+{
+   /** Class <code>Logger</code>, for trace messages. */
+   private static Logger errorLog = 
+      Logger.getLogger(BatchRecoveryLogReader.class);
+
+   /** The log file read by this reader. */
+   private File logFile;
+   
+   /** Xid factory for converting local transaction ids into global ids. */
+   private XidFactoryBase xidFactory;
+   
+   /**
+    * Constructs a <code>BatchRecoveryLogReader</code>.
+    * 
+    * @param logFile the log file that will be read by the reader 
+    * @param xidFactory Xid factory that the reader will use to convert local 
+    *                   transaction ids into global ids.
+    */
+   BatchRecoveryLogReader(File logFile, XidFactoryBase xidFactory)
+   {
+      this.logFile = logFile;
+      this.xidFactory = xidFactory;
+   }
+
+   /**
+    * Reads the header object written out to the beginning of the log file via
+    * Java serialization.
+    * 
+    * @param dis a <code>DataInputStream</code> view of the log file
+    * @return the header object read from the log file.
+    * @throws IOException if there is an error reading the header object 
+    * @throws ClassNotFoundException if the class of the header object is not
+    *                     available.
+    */
+   private Object readHeaderObject(DataInputStream dis) 
+         throws IOException, 
+                ClassNotFoundException
+   {
+      int num = dis.readInt();
+      byte[] bytes = new byte[num];
+      dis.read(bytes);
+      ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
+      ObjectInputStream ois = new ObjectInputStream(bais);
+      return ois.readObject();
+   }
+
+   // RecoveryLogReader implementation ------------------------------
+
+   /**
+    * Gets the name of the underlying log file.
+    * 
+    * @return the name of the log file.
+    */
+   public String getLogFileName()
+   {
+      return logFile.toString();
+   }
+
+   /**
+    * Gets the branch qualifier string stored in the log file header.
+    * 
+    * @return the branch qualifier read from the log file header.
+    */
+   public String getBranchQualifier()
+   {
+      FileInputStream fis;
+      DataInputStream dis;
+
+      try
+      {
+         fis = new FileInputStream(logFile);
+         dis = new DataInputStream(fis);
+         try
+         {
+            return (String) readHeaderObject(dis);
+         }
+         finally
+         {
+            fis.close();
+         }
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException(e);
+      }
+   }
+
+   /**
+    * Recovers transaction information from the log file. 
+    * 
+    * @param committedSingleTmTransactions a <code>List</code> of 
+    *           <code>LogRecord.Data</code> instances with one element per
+    *           committed single-TM transaction logged to the log file
+    * @param committedMultiTmTransactions a <code>List</code> of 
+    *           <code>LogRecord.Data</code> instances with one element per
+    *           committed  multi-TM transaction that has not yet completed the
+    *           second phase of the 2PC protocol when the server crashed 
+    * @param inDoubtTransactions a <code>List</code> of 
+    *           <code>LogRecord.Data</code> instances with one element per
+    *           foreign transaction that arrived at the server via DTM/OTS
+    *           context propagation and was in the in-doubt state (i.e.,
+    *           replied to prepare with a commit vote but has not yet received
+    *           information on the transaction outcome) when the server crashed
+    * @param inDoubtJcaTransactions a <code>List</code> of 
+    *           <code>LogRecord.Data</code> instances with one element per
+    *           foreign transaction that arrived at the server via JCA 
+    *           transaction inflow and was in the in-doubt state (i.e., replied
+    *           to prepare with a commit vote and was waiting for information 
+    *           on the transaction outcome) when the server crashed.
+    */
+   public void recover(List committedSingleTmTransactions,
+                       List committedMultiTmTransactions,
+                       List inDoubtTransactions,
+                       List inDoubtJcaTransactions)
+   {
+      Map activeMultiTmTransactions = new HashMap();
+      FileInputStream fis;
+      DataInputStream dis;
+      CorruptedLogRecordException corruptedLogRecordException = null;
+
+      try
+      {
+         fis = new FileInputStream(logFile);
+         dis = new DataInputStream(fis);
+
+      }
+      catch (IOException e)
+      {
+         throw new RuntimeException(e);
+      }
+
+      try
+      {
+         readHeaderObject(dis); // branch qualifier
+         
+         if (fis.available() < LogRecord.FULL_HEADER_LEN)
+            return;
+         
+         FileChannel channel = fis.getChannel();
+         ByteBuffer buf = ByteBuffer.allocate(LogRecord.FULL_HEADER_LEN);
+         channel.read(buf);
+         
+         LogRecord.Data data;
+         int len = LogRecord.getNextRecordLength(buf, 0);
+
+         while (len > 0)
+         {
+            buf = ByteBuffer.allocate(len + LogRecord.FULL_HEADER_LEN); 
+            if (channel.read(buf) < len)
+            {
+               errorLog.info("Unexpected end of file in transaction log file " + 
+                             logFile.getName());
+               break;
+            }
+            buf.flip();
+            data = new LogRecord.Data();
+            try
+            {
+               LogRecord.getData(buf, len, data);
+            }
+            catch (CorruptedLogRecordException e)
+            {
+               // Save the exception only if no previous exception is saved.
+               if (corruptedLogRecordException == null)
+                  corruptedLogRecordException = e; 
+               long corruptedRecordPos = 
+                  channel.position() - buf.limit() - LogRecord.FULL_HEADER_LEN;
+               long nextPos = scanForward(corruptedRecordPos + 1);
+               if (nextPos == 0)
+               {
+                  errorLog.info("LOG CORRUPTION AT THE END OF LOG FILE " + 
+                                logFile.getName());
+                  break;
+               }
+               else
+               {
+                  errorLog.info("LOG CORRUPTION IN THE MIDDLE OF LOG FILE " + 
+                                logFile.getName() + ". Skipping " + 
+                                (nextPos - corruptedRecordPos) + " bytes" +
+                                ". Disabling presumed rollback.");
+                  channel.position(nextPos);
+                  buf = ByteBuffer.allocate(LogRecord.FULL_HEADER_LEN);
+                  channel.read(buf);
+                  len = LogRecord.getNextRecordLength(buf, 0);
+                  corruptedLogRecordException.disablePresumedRollback = true;
+                  continue;
+               }
+            }
+            switch (data.recordType)
+            {
+            case LogRecord.TX_COMMITTED:
+               data.globalTransactionId = 
+                  xidFactory.localIdToGlobalId(data.localTransactionId);
+               committedSingleTmTransactions.add(data);
+               break;
+            case LogRecord.MULTI_TM_TX_COMMITTED:
+               data.globalTransactionId = 
+                  xidFactory.localIdToGlobalId(data.localTransactionId);
+               // fall through
+            case LogRecord.TX_PREPARED:
+            case LogRecord.JCA_TX_PREPARED:
+               activeMultiTmTransactions.put(new Long(data.localTransactionId), 
+                                             data);
+               break;
+            case LogRecord.TX_END:
+               activeMultiTmTransactions.remove(
+                                             new Long(data.localTransactionId));
+               break;
+            default:
+               errorLog.warn("INVALID TYPE IN LOG RECORD.");
+               break;
+            }
+            try
+            {
+               len = LogRecord.getNextRecordLength(buf, len);
+            }
+            catch (CorruptedLogRecordException e)
+            {
+               // Save the exception only if no previous exception is saved.
+               if (corruptedLogRecordException == null)
+                  corruptedLogRecordException = e; 
+               long corruptedRecordPos = 
+                  channel.position() - buf.limit() - LogRecord.FULL_HEADER_LEN;
+               long nextPos = scanForward(corruptedRecordPos + 1);
+               if (nextPos == 0)
+               {
+                  errorLog.info("LOG CORRUPTION AT THE END OF LOG FILE " + 
+                                logFile.getName());
+                  len = 0;
+               }
+               else
+               {
+                  errorLog.info("LOG CORRUPTION IN THE MIDDLE OF LOG FILE " + 
+                                logFile.getName() + ". Skipping " + 
+                                (nextPos - corruptedRecordPos) + " bytes" +
+                                ". Disabling presumed rollback.");
+                  channel.position(nextPos);
+                  buf = ByteBuffer.allocate(LogRecord.FULL_HEADER_LEN);
+                  channel.read(buf);
+                  len = LogRecord.getNextRecordLength(buf, 0);
+                  corruptedLogRecordException.disablePresumedRollback = true;
+               }
+            }
+         }
+         
+         Iterator iter = activeMultiTmTransactions.values().iterator();
+         while (iter.hasNext())
+         {
+            data = (LogRecord.Data) iter.next();
+            
+            switch (data.recordType)
+            {
+            case LogRecord.MULTI_TM_TX_COMMITTED:
+               committedMultiTmTransactions.add(data); 
+               break;
+            case LogRecord.TX_PREPARED:
+               inDoubtTransactions.add(data);
+               break;
+            case LogRecord.JCA_TX_PREPARED:
+               inDoubtJcaTransactions.add(data);
+               break;
+            default:
+               errorLog.warn("INCONSISTENT STATE.");
+               break;
+            }
+         }
+         
+         if (corruptedLogRecordException != null)
+            throw corruptedLogRecordException;
+      }
+      catch (IOException e)
+      {
+         errorLog.warn("Unexpected exception in recover:", e);
+      }
+      catch (ClassNotFoundException e)
+      {
+         errorLog.warn("Unexpected exception in recover:", e);
+      }
+      try
+      {
+         fis.close();
+      }
+      catch (IOException e)
+      {
+         throw new RuntimeException(e);
+      }
+   }
+   
+   /**
+    * Removes the log file.
+    */
+   public void finishRecovery()
+   {
+      logFile.delete();
+   }
+
+   /**
+    * Scans the log file for a valid log record. This is a helper method for
+    * log file corruption handling.  
+    * 
+    * @param pos the file position where the forward scan should start. 
+    * @return the file position in which a valid log record was found, or
+    *         0 if end of file was reached and no valid log record was found. 
+    */
+   private long scanForward(long pos)
+   {
+      errorLog.trace("entering scanForward");
+      RandomAccessFile file = null;
+      
+      try
+      {
+         file = new RandomAccessFile(logFile, "r");
+         
+         while (pos + LogRecord.FULL_HEADER_LEN < logFile.length())
+         {
+            if (match(file, pos, LogRecord.HEADER))
+            {
+               errorLog.trace("scanForward: match at pos=" + pos);
+               file.seek(pos + LogRecord.HEADER_LEN);
+               short recLen = file.readShort();
+               errorLog.trace("scanForward: recLen=" + recLen);
+               if (pos + LogRecord.FULL_HEADER_LEN + recLen < logFile.length())
+               {
+                  byte[] buf = new byte[recLen];
+                  file.seek(pos + LogRecord.FULL_HEADER_LEN);
+                  file.read(buf, 0, recLen);
+                  if (LogRecord.hasValidChecksum(buf))
+                  {
+                     errorLog.trace("scanForward: returning " + pos);
+                     return pos;
+                  }
+                  else
+                  {
+                     errorLog.trace("scanForward: " +
+                                    "bad checksum in record at pos=" + pos);
+                     pos += LogRecord.HEADER_LEN;
+                  }
+               }
+               else
+                  pos =+ LogRecord.HEADER_LEN;
+            }
+            else
+               pos++;
+         }
+         errorLog.trace("scanForward: returning 0");
+         return 0;
+      }
+      catch (FileNotFoundException e)
+      {
+         errorLog.warn("Unexpected exception in scanForward:", e);
+         return 0;
+      }
+      catch (IOException e)
+      {
+         errorLog.warn("Unexpected exception in scanForward:", e);
+         return 0;
+      }
+      finally
+      {
+         if (file != null)
+         {
+            try
+            {
+               file.close();
+            }
+            catch (IOException e)
+            {
+               errorLog.warn("Unexpected exception in scanForward:", e);
+            }
+         }
+      }
+   }
+   
+   /**
+    * Returns true if the byte sequence that starts at given position of a
+    * file matches a given byte array.
+    * 
+    * @param file a random access file open for reading
+    * @param pos the file position of the byte sequence to be compared against 
+    *            the <code>pattern</code> byte array 
+    * @param pattern the byte pattern to match.
+    * @return true if the pattern appears at the specified position of the
+    *         file, and false otherwise/
+    * @throws IOException if there is a problem reading the file.
+    */
+   private static boolean match(RandomAccessFile file, long pos, byte[] pattern)
+      throws IOException
+   {
+      for (int i = 0; i < pattern.length; i++)
+      {
+         file.seek(pos + i);
+         if (file.readByte() != pattern[i])
+            return false;
+      }
+      return true;
+   }
+   
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogger.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogger.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLogger.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,403 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import java.io.File;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+
+import javax.transaction.xa.Xid;
+
+import org.jboss.tm.XidFactoryBase;
+
+/**
+ * This <code>RecoveryLogger</code> implementation 
+ * uses <code>BatchWriter</code>s that batch log write requests 
+ * in order to minimize disk forcing activity. A 
+ * <code>BatchRecoveryLogger</code> instance is the "main object" of the
+ * the recovery logger service, which is simply an MBean wrapper for that
+ * instance.
+ *   
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class BatchRecoveryLogger 
+      implements RecoveryLogger
+{
+   /** Array of names of directories that contain recovery log files. */ 
+   private String[] stringDirectoryList;
+   
+   /** Array of directories that contain recovery log files. */
+   private File[] directoryList;
+   
+   /** The constant size of a recovery log file. */
+   private int logFileSize;
+   
+   /** Array of recovery log files found at recovery time.  */
+   private File[] existingRecoveryLogFiles;
+   
+   /** Array of log writers (one per directory that contains log files). */
+   private BatchWriter[] writers;
+   
+   /** Number of log writers (number of directories that contain log files). */
+   private int numWriters;
+   
+   /** Sequential number used to choose a log writer */
+   private volatile int seqNo = 0;
+   
+   /** Name of the directory that contains heuristic status log files. */
+   private String heuristicStatusLogDirectoryName;
+   
+   /** Directory that contain heuristic status log files. */
+   private File heuristicStatusLogDirectory;
+   
+   /** Array of heuristic status log files found at recovery time.  */
+   private File[] existingHeuristicStatusLogFiles;
+   
+   /** Heuristic status log writer. */
+   private HeuristicStatusLog heuristicStatusLogger;
+   
+   /** The server's Xid factory. */
+   private XidFactoryBase xidFactory;
+
+   /** For asynchronously restarting <code>BatchLog</code> instances. */
+   private LogRestarter logCleaner;
+   
+   // Package protected methods -------------------------------------
+   // (The BatchRecoveryLoggerService calls these methods.)
+  
+   /*
+    * Gets the names of directories that contain recovery log files.
+    * 
+    * @return an array of names of directories that contain recovery log 
+    *         files.
+    * 
+    */
+   String[] getDirectoryList()
+   {
+      return stringDirectoryList;
+   }
+
+   /**
+    * Sets the names of directories that contain recovery log files.
+    * <p />
+    * This method used to be package protected. It was changed to public just
+    * for testing purposes.
+    * 
+    * @param directoryList array of names of directories that contain recovery 
+    *                      log files.
+    * 
+    */
+   public void setDirectoryList(String[] directoryList)
+   {
+      this.stringDirectoryList = directoryList;
+      File[] list = new File[directoryList.length];
+      for (int i = 0; i < directoryList.length; i++)
+      {
+         list[i] = new File(directoryList[i]);
+         list[i] = list[i].getAbsoluteFile();
+         if (!list[i].exists())
+         {
+            if (!list[i].mkdirs())
+            {
+               throw new RuntimeException("Unable to create recovery directory: " 
+                                          + directoryList[i]);
+            }
+         }
+      }
+      this.directoryList = list;
+   }
+
+   /**
+    * Gets the constant size of a log file.
+    * 
+    * @return the constans size of a log file, in bytes.
+    */
+   int getLogFileSize()
+   {
+      return logFileSize;
+   }
+
+   /**
+    * Sets the constant size of a log file.
+    * <p />
+    * This method used to be package protected. It was changed to public just
+    * for testing purposes.
+    * 
+    * @param logFileSize the constant size of a log file, in bytes.
+    */
+   public void setLogFileSize(int logFileSize)
+   {
+      this.logFileSize = logFileSize;
+   }
+   
+   /**
+    * Gets the name of the directory that contains heuristic status log files.
+    *
+    * @return the name of the directory that contains heuristic status log 
+    *         files.
+    */
+   String getHeuristicStatusLogDirectory()
+   {
+      return heuristicStatusLogDirectoryName;
+   }
+
+   /**
+    * Sets the name of the directory that contains heuristic status log files.
+    * <p />
+    * This method used to be package protected. It was changed to public just
+    * for testing purposes.
+    * 
+    * @param heuristicStatusLogDirectoryName the name of the directory that 
+    *              contains heuristic status log files.
+    */
+   public void setHeuristicStatusLogDirectory(String heuristicStatusLogDirectoryName)
+   {
+      this.heuristicStatusLogDirectoryName = heuristicStatusLogDirectoryName;
+      heuristicStatusLogDirectory = new File(heuristicStatusLogDirectoryName);
+      heuristicStatusLogDirectory = 
+         heuristicStatusLogDirectory.getAbsoluteFile();
+      if (!heuristicStatusLogDirectory.exists())
+      {
+         if (!heuristicStatusLogDirectory.mkdirs())
+         {
+            throw new RuntimeException("Unable to create heuristic status " +
+                                       "log directory: "  
+                                       + heuristicStatusLogDirectoryName);
+         }
+      }
+   }
+
+   /**
+    * Gets the Xid factory.
+    * 
+    * @return the Xid factory.
+    */
+   XidFactoryBase getXidFactory()
+   {
+      return xidFactory;
+   }
+   
+   /**
+    * Sets the Xid factory
+    * <p />
+    * This method used to be package protected. It was changed to public just
+    * for testing purposes.
+    * 
+    * @param xidFactory the Xid factory.
+    */
+   public void setXidFactory(XidFactoryBase xidFactory)
+   {
+      this.xidFactory = xidFactory;
+   }
+
+   /**
+    * Starts this <code>BatchRecoveryLoggger</code>.
+    * <p />
+    * This method used to be package protected. It was changed to public just
+    * for testing purposes.
+    */
+   public void start() throws Exception
+   {
+      ArrayList list = new ArrayList();
+      for (int i = 0; i < directoryList.length; i++)
+      {
+         File dir = directoryList[i];
+         File[] files = dir.listFiles();
+         for (int j = 0; j < files.length; j++)
+         {
+            list.add(files[j]);
+         }
+      }
+      existingRecoveryLogFiles = (File[]) list.toArray(new File[list.size()]);
+
+      logCleaner = new LogRestarter(); 
+      new Thread(logCleaner, "Log file cleaner").start();
+
+      writers = new BatchWriter[directoryList.length];
+      String branchQualifier = xidFactory.getBranchQualifier();
+      for (int i = 0; i < directoryList.length; i++)
+      {
+         writers[i] = new BatchWriter(branchQualifier, logFileSize / 128, 
+                                   directoryList[i], logFileSize, logCleaner);
+         new Thread(writers[i], "Batch Recovery Log " + i).start();
+      }
+      numWriters = writers.length;
+      
+      existingHeuristicStatusLogFiles = heuristicStatusLogDirectory.listFiles();
+      heuristicStatusLogger = 
+         new HeuristicStatusLog(heuristicStatusLogDirectory);
+   }
+
+   /**
+    * Stops this <code>BatchRecoveryLoggger</code>.
+    * <p />
+    * This method used to be package protected. It was changed to public just
+    * for testing purposes.
+    */
+   public void stop() throws Exception
+   {
+      for (int i = 0; i < writers.length; i++)
+      {
+         writers[i].stop();
+      }
+      logCleaner.stop();
+      heuristicStatusLogger.close();
+   }
+
+   // RecoveryLogger implementation ---------------------------------
+
+  /**
+   * @see org.jboss.tm.recovery.RecoveryLogger#saveCommitDecision(
+   *            long, java.lang.String[])
+   */
+   public TxCompletionHandler saveCommitDecision(long localTransactionId,
+                                                 String[] resources)
+   {
+      ByteBuffer buffer;
+      
+      if (resources == null || resources.length == 0)
+      {
+         buffer = LogRecord.createTxCommittedRecord(localTransactionId);
+         return writers[++seqNo % numWriters].addBatch(buffer, false);
+      }
+      else
+      {
+         buffer = LogRecord.createTxCommittedRecord(localTransactionId,
+                                                    resources);
+         return writers[++seqNo % numWriters].addBatch(buffer, true);
+      }
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.RecoveryLogger#savePrepareDecision(
+    *            long, int, byte[], java.lang.String, java.lang.String[])
+    */
+   public TxCompletionHandler savePrepareDecision(long localTransactionId,
+                                                  int inboundFormatId,
+                                                  byte[] globalTransactionId,
+                                                  String recoveryCoordinator,
+                                                  String[] resources)
+   {
+      ByteBuffer buffer = LogRecord.createTxPreparedRecord(localTransactionId,
+                                                           inboundFormatId,
+                                                           globalTransactionId,
+                                                           recoveryCoordinator,
+                                                           resources);
+      return writers[++seqNo % numWriters].addBatch(buffer, true);
+   }
+   
+   /**
+    * @see org.jboss.tm.recovery.RecoveryLogger#savePrepareDecision(
+    *            long, javax.transaction.xa.Xid, java.lang.String[])
+    */
+   public TxCompletionHandler savePrepareDecision(long localTransactionId,
+                                                  Xid inboundXid,
+                                                  String[] resources)
+   {
+      ByteBuffer buffer = 
+         LogRecord.createJcaTxPreparedRecord(localTransactionId,
+                                            inboundXid,
+                                            resources);
+      return writers[++seqNo % numWriters].addBatch(buffer, true);
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.RecoveryLogger#saveHeuristicStatus(
+    *            long, boolean, int, byte[], byte[], int, int, boolean int[], 
+    *            org.jboss.tm.recovery.HeuristicStatus[])
+    */
+   public void saveHeuristicStatus(long localTransactionId,
+                                   boolean foreignTx,
+                                   int formatId, 
+                                   byte[] globalTransactionId,
+                                   byte[] inboundBranchQualifier,
+                                   int transactionStatus,
+                                   int heurStatusCode,
+                                   boolean locallyDetectedHeuristicHazard,
+                                   int[] xaResourceHeuristics, 
+                                   HeuristicStatus[] remoteResourceHeuristics)
+   {
+      ByteBuffer buffer = 
+         LogRecord.createHeurStatusRecord(localTransactionId,
+                                         foreignTx,
+                                         formatId, 
+                                         globalTransactionId,
+                                         inboundBranchQualifier,
+                                         transactionStatus,
+                                         heurStatusCode,
+                                         locallyDetectedHeuristicHazard,
+                                         xaResourceHeuristics,
+                                         remoteResourceHeuristics);
+      heuristicStatusLogger.write(buffer);
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.RecoveryLogger#clearHeuristicStatus(long)
+    */
+   public void clearHeuristicStatus(long localTransactionId)
+   {
+      ByteBuffer buffer =
+         LogRecord.createHeurForgottenRecord(localTransactionId);
+      heuristicStatusLogger.write(buffer);
+   }
+   
+   /**
+    * @see org.jboss.tm.recovery.RecoveryLogger#getRecoveryLogs()
+    */
+   public RecoveryLogReader[] getRecoveryLogs()
+   {
+      if (existingRecoveryLogFiles == null 
+            || existingRecoveryLogFiles.length == 0)
+         return null;
+
+      RecoveryLogReader[] readers = 
+         new RecoveryLogReader[existingRecoveryLogFiles.length];
+      for (int i = 0; i < readers.length; i++)
+      {
+         readers[i] = 
+            new BatchRecoveryLogReader(existingRecoveryLogFiles[i], xidFactory);
+      }
+      return readers;
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.RecoveryLogger#getHeuristicStatusLogs()
+    */
+   public HeuristicStatusLogReader[] getHeuristicStatusLogs()
+   {
+      if (existingHeuristicStatusLogFiles == null 
+            || existingHeuristicStatusLogFiles.length == 0)
+         return null;
+
+      HeuristicStatusLogReader[] readers = 
+         new HeuristicStatusLogReader[existingHeuristicStatusLogFiles.length];
+      for (int i = 0; i < readers.length; i++)
+      {
+         readers[i] = new SimpleHeuristicStatusLogReader(
+                                          existingHeuristicStatusLogFiles[i]);
+      }
+      return readers;
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerService.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerService.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerService.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,167 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import javax.management.ObjectName;
+
+import org.jboss.system.ServiceMBeanSupport;
+import org.jboss.tm.XidFactoryMBean;
+
+/**
+ * Service MBean that provides recovery logger functionality. The
+ * <code>BatchRecoveryLoggerService</code> is simply an MBean wrapper for a 
+ * <code>BatchRecoveryLogger</code> instance, which does the real work.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public class BatchRecoveryLoggerService  
+      extends ServiceMBeanSupport 
+      implements BatchRecoveryLoggerServiceMBean
+{
+   /** The actual recovery logger. */
+   protected BatchRecoveryLogger logger;
+   
+   /** The recovery logger's Xid factory. */ 
+   private ObjectName xidFactory;
+
+   /**
+    * Constructs a <code>BatchRecoveryLoggerService</code>.
+    */
+   public BatchRecoveryLoggerService()
+   {
+      logger = createLogger();
+   }
+   
+   /**
+    * To be overrided by subclasses that create a logger derived from
+    * <code>BatchRecoveryLogger</code> for testing purposes.
+    */
+   protected BatchRecoveryLogger createLogger()
+   {
+      return new BatchRecoveryLogger();
+   }
+
+   // ServiceMBeanSupport overrides ---------------------------------
+  
+   /**
+    * @see org.jboss.system.ServiceMBeanSupport#startService()
+    */
+   public void startService() throws Exception
+   {
+      super.startService();
+      XidFactoryMBean xidFactoryObj = 
+         (XidFactoryMBean) getServer().getAttribute(xidFactory, "Instance");
+      logger.setXidFactory(xidFactoryObj);
+      logger.start();
+   }
+
+   /**
+    * @see org.jboss.system.ServiceMBeanSupport#stopService()
+    */
+   public void stopService() throws Exception
+   {
+      super.stopService();
+      logger.stop();
+   }
+
+   // RecoveryLoggerInstance implementation -------------------------
+   
+   /**
+    * @see org.jboss.tm.recovery.RecoveryLoggerInstance#getInstance()
+    */
+   public RecoveryLogger getInstance()
+   {
+      return logger;
+   }
+
+   // BatchRecoveryLoggerServiceMBean implementation ----------------
+   
+   /**
+    * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#getDirectoryList()
+    */
+   public String[] getDirectoryList()
+   {
+      return logger.getDirectoryList();
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#setDirectoryList(
+    *                                                       java.lang.String[])
+    */
+   public void setDirectoryList(String[] directoryList)
+   {
+      logger.setDirectoryList(directoryList);
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#getLogFileSize()
+    */
+   public int getLogFileSize()
+   {
+      return logger.getLogFileSize();
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#setLogFileSize(
+    *                                                                       int)
+    */
+   public void setLogFileSize(int logFileSize)
+   {
+      logger.setLogFileSize(logFileSize);
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#getHeuristicStatusLogDirectory()
+    */
+   public String getHeuristicStatusLogDirectory()
+   {
+      return logger.getHeuristicStatusLogDirectory();
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#setHeuristicStatusLogDirectory(
+    *                                                          java.lang.String)
+    */
+   public void setHeuristicStatusLogDirectory(String directory)
+   {
+      logger.setHeuristicStatusLogDirectory(directory);
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#getXidFactory()
+    */
+   public ObjectName getXidFactory()
+   {
+      return xidFactory;
+   }
+   
+   /**
+    * @see org.jboss.tm.recovery.BatchRecoveryLoggerServiceMBean#setXidFactory(
+    *                                               javax.management.ObjectName)
+    */
+   public void setXidFactory(ObjectName xidFactory)
+   {
+      this.xidFactory = xidFactory;
+   }
+   
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerServiceMBean.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerServiceMBean.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/BatchRecoveryLoggerServiceMBean.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,100 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import javax.management.ObjectName;
+
+import org.jboss.system.ServiceMBean;
+
+/**
+ * MBean interface of the batch recovery logger service.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public interface BatchRecoveryLoggerServiceMBean 
+      extends RecoveryLoggerInstance, 
+              ServiceMBean
+{
+   /*
+    * Gets the names of directories that contain recovery log files.
+    * 
+    * @return an array of names of directories that contain recovery log 
+    *         files.
+    * 
+    */
+   String[] getDirectoryList();
+
+   /**
+    * Sets the names of directories that contain recovery log files.
+    * 
+    * @param directoryList array of names of directories that contain recovery 
+    *                      log files.
+    * 
+    */
+   void setDirectoryList(String[] directoryList);
+
+   /**
+    * Gets the constant size of a log file.
+    * 
+    * @return the constans size of a log file, in bytes.
+    */
+   int getLogFileSize();
+
+   /**
+    * Sets the constant size of a log file.
+    * 
+    * @param logFileSize the constant size of a log file, in bytes.
+    */
+   void setLogFileSize(int logFileSize);
+   
+   /**
+    * Gets the name of the directory that contains heuristic status log files.
+    *
+    * @return the name of the directory that contains heuristic status log 
+    *         files.
+    */
+   String getHeuristicStatusLogDirectory();
+
+   /**
+    * Sets the name of the directory that contains heuristic status log files.
+    * 
+    * @param heuristicStatusLogDirectoryName the name of the directory that 
+    *              contains heuristic status log files.
+    */
+   void setHeuristicStatusLogDirectory(String directory);
+
+   /**
+    * Gets the Xid factory.
+    * 
+    * @return the Xid factory.
+    */
+   ObjectName getXidFactory();
+
+   /**
+    * Sets the Xid factory
+    * 
+    * @param xidFactory the Xid factory.
+    */
+   void setXidFactory(ObjectName xidFactory);
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/BatchWriter.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/BatchWriter.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/BatchWriter.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,469 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import EDU.oswego.cs.dl.util.concurrent.Latch;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.jboss.logging.Logger;
+
+/**
+ * This class batches log write requests in order to minimize disk forcing 
+ * activity. A <code>BatchWriter</code> has a current <code>BatchLog</code> 
+ * instance, to which it writes log records, plus a pool of clean 
+ * <code>BatchLog</code> instances, to be used when the current  
+ * <code>BatchLog</code> gets full. It maintains a queue of write requests 
+ * and implements a writer thread that takes write requests from the queue
+ * and batches them together in order to minimize disk forces.
+ * <p>
+ * The <code>BatchLog</code> instances owned by a <code>BatchWriter</code>
+ * know their owner <code>BatchWriter</code> and call the methods
+ * <code>restartBatchLog</code> and <code>getNextLogs</code> on their owner. 
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>    
+ * @version $Revision: 37459 $
+ */
+class BatchWriter implements Runnable
+{
+
+   /** Class <code>Logger</code> for trace messages. */
+   private static Logger errorLog = Logger.getLogger(BatchWriter.class);
+
+   /** True if trace messages should be logged. */
+   private static boolean traceEnabled = errorLog.isTraceEnabled();
+
+   /** The directory in which log files will be created. */
+   private final File dir;
+   
+   /** The initial capacity of the queue of write requests. */
+   private final int initialCapacity;
+   
+   /** The current <code>BatchLog</code> instance. */
+   private BatchLog log;
+   
+   /** The constant size of a recovery log file. */
+   private final int fileSize;
+   
+   /** The header object written to the beginning of a log file. */
+   private Object header;
+
+   /** Exception thrown when switching the current log file. */
+   private Exception abort;
+   
+   /** This flag can be set to false to stop the writer thread. */  
+   private boolean running = true;
+   
+   /** Pool of clean <code>BatchLog</code> instances. */ 
+   private final List nextLogs = Collections.synchronizedList(new ArrayList());
+   
+   /** Synchronizes accesses to the <code>currentQueue</code> of requests. */ 
+   private Object batchLock = new Object();
+   
+   /** The current queue of write requests */
+   private ArrayList currentQueue;
+   
+   /** Latch released when a batch of write requests are performed. */
+   private Latch currentLatch;
+   
+   /** For asynchronously restarting <code>BatchLog</code> instances. */
+   private LogRestarter logRestarter;
+
+   /**
+    * Constructs a <code>BatchWriter</code>.
+    * 
+    * @param header the header object to be placed at the beginning of the
+    *               <code>BatchLog</code>s owned by this 
+    *               <code>BatchWriter</code> 
+    * @param initialCapacity the initial capacity (in bytes) of the queue of 
+    *               write requests
+    * @param dir the directory in which log files will be created
+    * @param fileSize the constant size (in bytes) of a log file
+    * @param logRestarter <code>LogRestarter</code> instance that the  
+    *                     <code>BatchWriter</code> will use for asynchronously 
+    *                     restarting <code>BatchLog</code> instances.
+    * @throws IOException if a log file could not be created or initialized. 
+    */
+   BatchWriter(Object header, int initialCapacity, 
+               File dir, int fileSize, LogRestarter logRestarter) 
+         throws IOException
+   {
+      this.header = header;
+      this.fileSize = fileSize;
+      this.initialCapacity = initialCapacity;
+      this.currentQueue = new ArrayList(initialCapacity);
+      this.currentLatch = new Latch();
+      this.dir = dir;
+      this.logRestarter = logRestarter;
+      log = new BatchLog(this, header, dir, fileSize);
+      nextLogs.add(new BatchLog(this, header, dir, fileSize));
+   }
+
+   /**
+    * Asynchronously restarts a given <code>BatchLog</code>. The 
+    * <code>BatchLog</code> instances owned by this <code>BatchWriter</code>
+    * call this method to asynchronously restart themselves. 
+    *  
+    * @param logToRestart the <code>BatchLog</code> to be asynchronously 
+    *                     restarted.
+    */
+   void restartBatchLog(BatchLog logToRestart)
+   {
+      logRestarter.add(logToRestart);
+   }
+
+   /**
+    * Gets the <code>List</code> that implements the pool of clean 
+    * <code>BatchLog</code> instances of this <code>BatchWriter</code>.
+    * The <code>BatchLog</code> instances owned by this 
+    * <code>BatchWriter</code> call this method after clean up, to add 
+    * themselves to pool of clean <code>BatchLog</code> instances.
+    * 
+    * @return the <code>List</code> of clean <code>BatchLog</code> instances
+    *         kept by this <code>BatchWriter</code>.
+    */
+   List getNextLogs()
+   {
+      return nextLogs;
+   }
+
+   // FIXME: nobody calls this method 
+   void clearAbort()
+   {
+      abort = null;
+   }
+
+   /**
+    * Stops the writer thread.
+    */
+   void stop()
+   {
+      synchronized (batchLock)
+      {
+         running = false;
+         batchLock.notifyAll();
+      }
+   }
+
+   /**
+    * Force-writes a "transaction committed" or "transaction prepared" log 
+    * record to the current <code>BatchLog</code>.
+    * 
+    * @param buffer a <code>ByteBuffer</code> containing a log record to
+    *               be written to the current <code>BatchLog</code> 
+    * @param expectEndRecord true if the log record requires another log record
+    *               (a <code>TX_END</code> record) to be eventually written
+    *               to the same <code>BatchLog</code>.
+    * @return a <code>TxCompletionHandler</code> that should be invoked at the
+    *               end of the second phase of the two-phase commit protocol
+    *               for the transaction that generated the log record.
+    */
+   TxCompletionHandler addBatch(ByteBuffer buffer, boolean expectEndRecord)
+   {
+      PendingWriteRequest request = null;
+      
+      if (traceEnabled)
+      {
+         errorLog.trace("Transaction log record:" + 
+                        HexDump.fromBuffer(buffer.array()));
+         errorLog.trace(LogRecord.toString(buffer));
+      }         
+      
+      synchronized (batchLock)
+      {
+         request = new PendingWriteRequest(buffer, 
+                                           currentLatch, 
+                                           expectEndRecord);
+         currentQueue.add(request);
+         batchLock.notify();
+      }
+      
+      TxCompletionHandler completionHandler = request.waitTilDone();
+      
+      if (!expectEndRecord)
+         return completionHandler;
+      else
+         return new TransactionCompletionLogger((BatchLog) completionHandler);
+   }
+
+   /**
+    * Writes a "transaction completed" (<code>TX_END</code>) record to the 
+    * specified <code>BatchLog</code>. Each <code>TX_END</code> record is 
+    * paired with a "transaction committed" or with a "transaction prepared" 
+    * record and should be written to the same <code>BatchLog</code> as the 
+    * record it is paired with. This method does not necessarily force the
+    * <code>TX_END</code> record to disk, but it might do so if that record 
+    * is batched together with other records, which may require a forced
+    * write. 
+    * 
+    * @param buffer a <code>ByteBuffer</code> containing a <code>TX_END</code>
+    *               record to be written to a given <code>BatchLog</code> 
+    * @param destinationLog the <code>BatchLog</code> to which the 
+    *               <code>TX_END</code> record will be written.
+    */
+   void addBatch(ByteBuffer buffer, BatchLog destinationLog)
+   {
+      PendingWriteRequest receipt = null;
+      
+      if (traceEnabled)
+      {
+         errorLog.trace("Transaction Log record:" + 
+                        HexDump.fromBuffer(buffer.array()));
+         errorLog.trace(LogRecord.toString(buffer));
+      }
+   
+      synchronized (batchLock)
+      {
+         receipt = new PendingWriteRequest(buffer, 
+                                           currentLatch,
+                                           destinationLog);
+         currentQueue.add(receipt);
+         batchLock.notify();
+      }
+   }
+
+   /**
+    * The writer thread body.
+    */
+   public void run()
+   {
+      ArrayList work = null;
+      Latch workLatch = null;
+
+      while (running)
+      {
+         synchronized (batchLock)
+         {
+            if (currentQueue.size() > 0)
+            {
+               if (work == null) 
+                  work = new ArrayList(initialCapacity);
+
+               // Switch currentQueue and work. All write requests that
+               // were in currentQueue will be handled as a batch of work.
+               
+               ArrayList tmp = work;
+               work = currentQueue;
+               currentQueue = tmp;
+               workLatch = currentLatch;
+               currentLatch = new Latch();
+            }
+            else
+            {
+               // Wait for a write request.
+               try
+               {
+                  batchLock.wait();
+               }
+               catch (InterruptedException ignored)
+               {
+                  if (!running) break;
+                  Thread.interrupted(); // clear interrupted
+               }
+               continue;
+            }
+         }
+
+         try
+         {
+            // Perform the batch of work.
+            doWork(work);
+         }
+         catch (Exception e)
+         {
+            break;
+         }
+         finally
+         {
+            // Let 
+            workLatch.release();
+            work.clear();
+         }
+      }
+      cleanup();
+   }
+
+   /**
+    * Performs a batch of write requests.
+    * 
+    * @param work an <code>ArrayList</code> of 
+    *        <code>PendingWriteRequest</code>s.
+    */
+   private void doWork(ArrayList work)
+   {
+      if (abort != null)
+      {
+         for (int i = 0; i < work.size(); i++)
+         {
+            PendingWriteRequest request = (PendingWriteRequest) work.get(i);
+            request.setFailure(abort);
+         }
+         return;
+      }
+
+      ByteBuffer[] records = new ByteBuffer[work.size()];
+      int offset = 0;
+      try
+      {
+         int length = records.length;
+         int usedSize = log.getPosition();
+         int numTransactions;
+         boolean done = false;
+         
+         while (!done)
+         {
+            int j;
+            numTransactions = 0;
+            
+            for (int i = j = offset; i < length; i++)
+            {
+               PendingWriteRequest request = (PendingWriteRequest) work.get(i);
+               int type = request.getType();
+               records[j] = request.getBuffer();
+               if (type != PendingWriteRequest.TYPE_END)
+               {
+                  usedSize += records[j].remaining();
+                  if (type == PendingWriteRequest.TYPE_TX_MULTI_TM)
+                     usedSize += LogRecord.TX_END_LEN;
+                  
+                  if (usedSize > fileSize)
+                  {  
+                     // will leave the for loop with work to do after restart  
+                     length = i;
+                  }
+                  else
+                  {
+                     numTransactions++;
+                     j++;
+                  }
+               }
+               else 
+               {
+                  // TX_END record: which log should we use?
+                  BatchLog requestedLog = request.getLogger();
+                  
+                  if (requestedLog != log)
+                  {
+                     // Use requestedLog instead of the current log
+                     requestedLog.write(records[j], true);
+                     // (Do not increment j in this case!)
+                  }
+                  else
+                  {
+                     // Let the record go to the current log 
+                     j++;
+                  }
+               }
+                  
+            }
+            done = (length == records.length);
+            length = length - offset;
+            log.write(records, offset, j - offset, numTransactions);
+            setCompletionHandler(offset, length, work);
+            if (!done)
+            {
+               restart();
+               offset = offset + length;
+               length = records.length;
+               usedSize = log.getPosition();
+            }
+         }
+      }
+      catch (IOException failure)
+      {
+         for (int i = offset; i < records.length - offset; i++)
+         {
+            PendingWriteRequest request = (PendingWriteRequest) work.get(i);
+            request.setFailure(failure);
+         }
+         if (abort == null)
+            restart();
+      }
+   }
+
+   /**
+    * Sets to the current <code>BatchLog</code> the completion handler of a 
+    * range of requests in an <code>ArrayList</code> of 
+    * <code>PendingWriteRequest</code>s.
+    *  
+    * @param offset index of the first request in the range
+    * @param length number of requests in the range
+    * @param work   an <code>ArrayList</code> of 
+    *               <code>PendingWriteRequest</code>s
+    */
+   private void setCompletionHandler(int offset, int length, ArrayList work)
+   {
+      for (int i = offset; i < length; i++)
+      {
+         PendingWriteRequest request = (PendingWriteRequest) work.get(i);
+         if (request.getType() != PendingWriteRequest.TYPE_END)
+            request.setCompletionHandler(log);
+      }
+   }
+
+   /**
+    * Closes all <code>BatchLog</code>s owned by this <code>BatchWriter</code>.   
+    */
+   private void cleanup()
+   {
+      synchronized (nextLogs)
+      {
+
+         for (int i = 0; i < nextLogs.size(); i++)
+         {
+            BatchLog nextLog = (BatchLog) nextLogs.get(i);
+            nextLog.close();
+         }
+      }
+      log.close();
+   }
+
+   /**
+    * Switch the current <code>BatchLog</code>.
+    */
+   private void restart()
+   {
+      log.markForRestart();
+
+      if (nextLogs.size() > 0)
+         log = (BatchLog) nextLogs.remove(0);
+      else
+      {
+         try
+         {
+            log = new BatchLog(this, header, dir, fileSize);
+         }
+         catch (IOException e)
+         {
+            abort = new Exception("FAILED TO RESTART RECOVERY LOG " +
+                                  "AFTER BEING FULL", e);
+         }
+      }
+   }
+   
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/CorruptedLogRecordException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/CorruptedLogRecordException.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/CorruptedLogRecordException.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,43 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+/**
+ * 
+ * A CorruptedLogRecordException.
+ * 
+ * @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class CorruptedLogRecordException 
+      extends RuntimeException
+{
+   /** The serialVersionUID */
+   private static final long serialVersionUID = -1711360962508810879L;
+   
+   boolean disablePresumedRollback = false;
+   
+   CorruptedLogRecordException(String message)
+   {
+      super(message);
+   }
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatus.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatus.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatus.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,61 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+/**
+ * "Struct class" that groups together the status code for a heuristic
+ * decision and a stringfied reference to the remote <code>Resource</code> 
+ * instance that reported the heuristic decision. 
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class HeuristicStatus
+{
+   /** 
+    * The status code for a heuristic decision. This field uses the same
+    * encoding as the <code>errorCode</code> field of a 
+    * <code>javax.transaction.xa.XAException</code> instance.  
+    */ 
+   public int code;
+   
+   /**
+    * A stringfied reference to the <code>Resource</code> that reported
+    * the heuristic decision.
+    */
+   public String resourceRef;
+   
+   /**
+    * Constructs a <code>HeuristicStatus</code>. 
+    *
+    * @param code the status code for a heuristic decision, encoded in the
+    *             same way as the <code>errorCode</code> field of a 
+    *             <code>javax.transaction.xa.XAException</code> instance
+    * @param resourceRef a stringfied reference to the <code>Resource</code> 
+    *             that reported the heuristic decision.
+    */
+   public HeuristicStatus(int code, String resourceRef)
+   {
+      this.code = code;
+      this.resourceRef = resourceRef;
+   }
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLog.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLog.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLog.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,133 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+
+import org.jboss.logging.Logger;
+
+/**
+ * Simple class that appends <code>ByteBuffer</code>s (each of which contains
+ * a heuristic status log record) to a log file. Information on heuristically 
+ * completed transactions does not go to the recovery logs -- it goes to a 
+ * separate log file. Since heuristic decisions are very rare, there is no need 
+ * to batch heuristic log writes together. 
+ * 
+ * TODO: In this implementation the heuristic status log file grows whenever
+ * a record is written to it. This is bad for robustness and should be replaced
+ * by an implementation in which the log file has constant lenght (such as the 
+ * recovery log implementation).  
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class HeuristicStatusLog
+{
+   /** 
+    * Class <code>Logger</code>, for trace messages.
+    */
+   private static Logger errorLog = 
+      Logger.getLogger(HeuristicStatusLog.class);
+
+   /** 
+    * True if trace messages should be logged.
+    */
+   private static boolean traceEnabled = errorLog.isTraceEnabled();
+
+   /** 
+    * The log file. 
+    */
+   private File logFile;
+   
+   /**
+    *  A <code>RandomAccessFile</code> view of the log file.
+    */
+   RandomAccessFile os;
+   
+   /**
+    * The <code>RandomAccessFile</code>'s underlying <code>FileChannel</code>.
+    */
+   private FileChannel channel;
+   
+   /**
+    * Constructs a new <code>HeuristicStatusLog</code>.
+    * 
+    * @param dir      the directory in which the log file will be created.
+    * @throws IOException
+    */
+   HeuristicStatusLog(File dir) 
+      throws IOException
+   {
+      logFile = File.createTempFile("HEURISTIC_STATUS_LOG", ".log", dir);
+      os = new RandomAccessFile(logFile, "rw");
+      channel = os.getChannel();
+      channel.force(true); 
+   }
+
+   /**
+    * Writes one record at the current position of this 
+    * <code>HeuristicStatusLog</code>.
+    * 
+    * @param record  a buffer with the record to be written
+    */
+   void write(ByteBuffer record) 
+   {
+      if (traceEnabled)
+      {
+         errorLog.trace("Heuristic status log record:" + 
+                        HexDump.fromBuffer(record.array()));
+         errorLog.trace(LogRecord.toString(record));
+      }
+      
+      try
+      {
+         channel.write(record);
+         channel.force(true);
+      }
+      catch (IOException e)
+      {
+         errorLog.error("Error writing heuristic status log " + 
+                        logFile.getName(), e);
+      }
+   }
+   
+   /**
+    * Closes this <code>HeuristicStatusLog</code>. 
+    */
+   void close()
+   {
+      try
+      {
+         os.close();
+      }
+      catch (IOException e)
+      {
+         errorLog.error("Error closing heuristic status log " + 
+                        logFile.getName(), e);
+      }
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLogReader.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLogReader.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/HeuristicStatusLogReader.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,60 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import java.util.Map;
+
+/**
+ * Interface that gives access to the information in a heuristic status 
+ * log file.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface HeuristicStatusLogReader
+{
+   /**
+    * Gets the name of the underlying heuristic status log file.
+    * 
+    * @return the name of the heuristic status log file.
+    */
+   String getLogFileName();
+
+   /**
+    * Recovers information on heuristically completed transactions from 
+    * the heuristic status log file.
+    *
+    * @param heuristicallyCompletedTransactions a <code>Map</code> to which
+    *              this method will one entry per heuristically completed
+    *              transaction. The map keys are <code>Long</code> values 
+    *              containing local transaction ids. The map values are
+    *              <code>LogRecord.HeurData</code> objects with information
+    *              on heuristically completed transactions.
+    */ 
+   void recover(Map heuristicallyCompletedTransactions);
+
+   /**
+    * Removes the underlying heuristic status log file.
+    */
+   void finishRecovery();
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/HexDump.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/HexDump.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/HexDump.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,133 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import java.io.*;
+
+/**
+ * Utility class for converting a byte array into a hex dump string.  
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class HexDump 
+{
+   /**
+    * Converts a byte array into an hex dump string.
+    * 
+    * @param buffer the byte array to be converted
+    * @return       a string containing the hex dump of the <code>buffer</code>.
+    */
+   public static String fromBuffer(byte[] buffer) 
+   {
+      int n;
+      final int N = 16;
+      StringBuffer sb = new StringBuffer("\n");
+
+      for (int i = 0; (n = Math.min(N, buffer.length - i)) != 0; i = i + n) 
+      {
+         sb.append(toHexString(i) + ": ");
+         for (int j = 0; j < n ; j++) 
+            sb.append(toHexString(buffer[i + j]) + " ");
+         for (int j = n; j < N; j++)
+            sb.append("   ");
+         sb.append("  ");
+         for (int j = 0; j < n ; j++) 
+            sb.append(toDisplayableChar(buffer[i + j]));
+         sb.append("\n");
+      }
+      return sb.toString();
+   }
+    
+   /**
+    * Converts a given byte into a string of two hex digits.
+    * @param b the byte to be converted
+    * @return  the string representation of <code>b</code>, 
+    *          as a pair of hex digits.
+    */
+   private static String toHexString(byte b) 
+   {
+      int i = b; // the byte 0x80 is sign extended to 0xffffff80
+      if (i < 0) 
+         i = i - (int) 0xffffff00; // undo sign extension 
+      String hexStr = Integer.toHexString(i);
+      return (hexStr.length() < 2) ? "0" + hexStr : hexStr;
+   }
+    
+   /**
+    * Converts a given integer into a string of up to 8 hex digits left-padded 
+    * with spaces. 
+    *
+    * @param n the <code>int</code> to be converted
+    * @return  a string containing the hex digits of <code>n</code> left-padded 
+    *          with spaces so that the total length of the string is 8.   
+    */   
+   private static String toHexString(int n) 
+   {
+      String hexStr = Integer.toHexString(n);
+      while (hexStr.length() < 8)
+         hexStr = " " + hexStr;
+      return hexStr;
+   }
+   
+   /**
+    * Converts a given byte into a displayable character.
+    * @param b the byte to convert
+    * @return  a char whose value is <code>b</code> (if <code>b</code> is in the
+    *          displayable range), or the char '.' (otherwise). 
+    */
+   private static char toDisplayableChar(byte b) 
+   {
+      char c = (char) b;
+      if (c >= 0x20 && c < 0x7f)
+         return c;
+      else
+         return '.';
+   }
+    
+   /**
+    * For testing. 
+    */
+   public static void main(String[] args) throws IOException 
+   {
+      InputStream in;
+      byte[] ibuf = new byte[4096];
+        
+      if (args.length > 1)
+      {
+         System.err.println("HexDump usage: java HexDump [file]"); 
+         System.exit(1);
+      }
+      in = (args.length == 0) ? System.in : new FileInputStream(args[0]);
+      int n = in.read(ibuf);
+      while (n > 0) 
+      {
+         byte[] buf = new byte[n];
+         System.arraycopy(ibuf, 0, buf, 0, n);
+         System.out.print(fromBuffer(buf));
+         n = in.read(ibuf);
+      }
+      if (args.length != 0)
+         in.close();
+   }
+    
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/LogRecord.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/LogRecord.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/LogRecord.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,1389 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.zip.Adler32;
+import java.util.zip.Checksum;
+
+import javax.transaction.xa.Xid;
+
+import org.jboss.tm.TxUtils;
+
+/**
+ * Utility class with static methods to create transaction log records and to
+ * extract information from transaction log records. It has static methods to
+ * create the following kinds of log records:
+ * <ul>
+ * <li><code>TX_COMMITTED</code> records, which are used for locally-started 
+ *     transactions that do not involve other transaction managers;</li>
+ * <li><code>MULTI_TM_TX_COMMITTED</code> records, which are used for 
+ *     locally-started transactions that involve other transaction 
+ *     managers;</li>
+ * <li><code>TX_PREPARED</code> records, which are used for foreign 
+ *     transactions that entered this virtual machine in transaction contexts 
+ *     propagated along with remote method invocations;</li>    
+ * <li><code>JCA_TX_PREPARED</code> records, which are used for foreign 
+ *     transactions that entered this virtual machine through JCA transaction 
+ *     inflow;</li>
+ * <li><code>TX_END</code> records, which are used for distributed 
+ *     transactions and mark the end of the second phase of the 2PC subtree 
+ *     coordinated by this transaction manager. They are paired with 
+ *     <code>MULTI_TM_TX_COMMITTED</code>, <code>TX_PREPARED</code>, and 
+ *     <code>JCA_TX_PREPARED</code> records. No <code>TX_END</code> record 
+ *     is written out in the case of a locally-started transaction that 
+ *     involves no external transaction managers. In other words, 
+ *     <code>TX_END</code> records are not paired with 
+ *     <code>TX_COMMITTED</code> records;</li>
+ *<li><code>HEUR_STATUS</code> records, which are used to log the 
+ *     heuristic status of a distributed transaction;</li>
+ *<li><code>HEUR_FORGOTTEN</code> records, which are used to clear the 
+ *     heuristic status of a distributed transaction.</li>
+ * </ul>
+ * Layout of <code>MULTI_TM_TX_COMMITTED</code> records:
+ * <pre>
+ *     - magicHeader (an array of HEADER_LEN bytes)
+ *     - recordLength (short)
+ *     - recordLengthCheck (short)
+ *     - recordType (byte)
+ *     - localTransactionId (long) 
+ *     - countOfDirEntries (N, a short)
+ *     - varField 0    \
+ *       ...            | <------------ variable-sized fields
+ *     - varField N-1  /
+ *     - offset of varField N-1  \      directory of varFields: N dir entries
+ *     - length of varField N-1   |     (each entry contains the length of
+ *       ...                      | <--  a varField and the offset of its 
+ *     - offset of varField 0     |      first byte relative to the start
+ *     - length of varField 0    /       of the record)
+ *     - checksum (int)
+ * </pre>
+ * The varFields of a <code>MULTI_TM_TX_COMMITTED</code> record contain 
+ * stringfied references for the remote <code>Resource</code>s enlisted in the 
+ * transaction. Note that the dir entries are stored backwards at the end of 
+ * the record, that is, the one that appears last (just before the checksum) 
+ * refers to the first varField, and the one that appears first refers to the 
+ * last varField.
+ * <p> 
+ * Layout of <code>TX_PREPARED</code> and <code>JCA_TX_PREPARED</code> 
+ * records:
+ * <pre>
+ *     - magicHeader (an array of HEADER_LEN bytes)
+ *     - recordLength (short)
+ *     - recordLengthCheck (short)
+ *     - recordType (byte)
+ *     - localTransactionId (long) 
+ *     - countOfDirEntries (N, a short)
+ *     - inboundFormatId (int)
+ *     - gidLength (short)
+ *     - globalTransactionId (an array of gidLength bytes)
+ *     - varField 0    \
+ *       ...            | <------------ variable-sized fields
+ *     - varField N-1  /
+ *     - offset of varField N-1  \      directory of varFields: N dir entries
+ *     - length of varField N-1   |     (each entry contains the length of
+ *       ...                      | <--  a varField and the offset of its 
+ *     - offset of varField 0     |      first byte relative to the start
+ *     - length of varField 0    /       of the record)
+ *     - checksum (int)
+ * </pre>
+ * The inboundFormatId is the formatId of the foreign <code>Xid</code>.
+ * In a <code>TX_PREPARED</code> record, the first varField (the one referred 
+ * to by the dir entry that immediately precedes the checksum) contains a 
+ * stringfied reference for the <code>RecoveryCoordinator</code> of the 
+ * transaction. 
+ * In a <code>JCA_TX_PREPARED</code> record, the first varField contains the 
+ * inbound branch qualifier, which is the branch qualifier part of the foreign 
+ * <code>Xid</code> passed to <code>XATerminator.prepare()</code>. 
+ * The remaining varFields of a <code>TX_PREPARED</code> or 
+ * <code>JCA_TX_PREPARED</code> records contain stringfied references for the
+ * remote <code>Resource</code>s enlisted in the transaction.
+ * </p> 
+ * Layout of <code>TX_COMMITTED</code> records:
+ * <pre>
+ *     - magicHeader (an array of HEADER_LEN bytes)
+ *     - recordLength (short)
+ *     - recordLengthCheck (short)
+ *     - recordType (byte)
+ *     - localTransactionId (long), 
+ *     - checksum (int)
+ * </pre>
+ * <p> 
+ * Layout of <code>TX_END</code> records:
+ * <pre>
+ *     - magicHeader (an array of HEADER_LEN bytes)
+ *     - recordLength (short)
+ *     - recordLengthCheck (short)
+ *     - recordType (byte)
+ *     - localTransactionId (long), 
+ *     - checksum (int)
+ * </pre>
+ * <p> 
+ * Layout of <code>HEUR_STATUS</code> records:
+ * <pre>
+ *     - magicHeader (an array of HEADER_LEN bytes)
+ *     - recordLength (short)
+ *     - recordLengthCheck (short)
+ *     - recordType (byte)
+ *     - localTransactionId (long)
+ *     - countOfDirEntries (N, a short)
+ *     - transactionStatus (byte)
+ *     - heuristicStatusCode (byte)
+ *     - locallyDetectedHeuristicHazardFlag (byte)
+ *     - isForeignTx (byte)
+ *     - formatId (int)
+ *     - gidLength (short)
+ *     - globalTransactionId (an array of gidLength bytes)
+ *     - varField 0    \
+ *       ...            | <------------ variable-sized fields
+ *     - varField N-1  /
+ *     - offset of varField N-1  \      directory of varFields: N dir entries
+ *     - length of varField N-1   |     (each entry contains the length of
+ *       ...                      | <--  a varField and the offset of its 
+ *     - offset of varField 0     |      first byte relative to the start
+ *     - length of varField 0    /       of the record)
+ *     - checksum (int)
+ * </pre>
+ * 
+ * In a <code>HEUR_STATUS</code> record, the first varField (the one referred 
+ * to by the dir entry that immediately precedes the checksum) contains the
+ * the inbound branch qualifier of a transaction that entered the server via 
+ * JCA inflow. This varField is empty (i.e., it has zero length) in the case of
+ * a transaction that did not enter the server via JCA inflow. The second 
+ * varField contains a byte array with the heuristic status codes of the XA 
+ * resources that reported heuristic decisions. This varField is empty if no
+ * XA resource reported heuristic decisions. The remaining varFields are 
+ * associated with remote resources that reported heuristic decisions. Each 
+ * such varField contains a byte with the heuristic status code reported by the
+ * remote resource, followed by a stringfied reference for the remote 
+ * <code>Resource</code> instance that reported a heuristic decision.  
+ * <p> 
+ * Layout of <code>HEUR_FORGOTTEN</code> records:
+ * <pre>
+ *     - magicHeader (an array of HEADER_LEN bytes)
+ *     - recordLength (short)
+ *     - recordLengthCheck (short)
+ *     - recordType (byte)
+ *     - localTransactionId (long), 
+ *     - checksum (int)
+ * </pre>
+ * <p> 
+ * Note that <code>TX_COMMITTED</code>, <code>TX_END</code>, and 
+ * <code>HEUR_FORGOTTEN</code> records have fixed size. The other types of 
+ * records are variable-sized.
+ * <p>
+ * In all record types:
+ * <ul>
+ * <li>The recordLength is the number of bytes of the part of the record that
+ *     follows the recordLengthCheck field. It counts the bytes from the 
+ *     recordType field up to (and including) the checksum field.</li>
+ * <li>The recordLengthCheck is the arithmetic negation of the recordLength
+ *     value (i.e,, -recordLength).</li>
+ * <li>The checksum is the Adler-32 checksum of the part of the record that
+ *     starts at the recordType field(which is included in the checksum) and 
+ *     ends at the checksum field (which is not included).</li>
+ * </ul>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public class LogRecord
+{
+   /** Magic header placed in all log records.*/
+   public static final byte[] HEADER = "Log".getBytes();
+   
+   /** Length of the magic header. */
+   public static final int HEADER_LEN = HEADER.length;
+   
+   /** Null header that will be read when there are no more log records */
+   private static final byte[] NULL_HEADER = {0, 0, 0};
+   
+   /** Size of a byte, in bytes. */
+   private static final int SIZEOF_BYTE = 1;
+   
+   /** Size of a short, in bytes. */
+   static final int SIZEOF_SHORT = 2;
+
+   /** Size of a long, in bytes. */
+   private static final int SIZEOF_LONG = 8;
+
+   /** Length of the inbound format id. */
+   private static final int FORMAT_ID_LEN = 4;
+   
+   /** Length of the checksum. */
+   private static final int CHKSUM_LEN = 4;
+   
+   /** Total length of the magic header plus record length fields. */
+   static final int FULL_HEADER_LEN = HEADER_LEN + 2 * SIZEOF_SHORT;
+   
+   /** Value of recordType field in a single-TM tx committed record */
+   static final byte TX_COMMITTED = (byte) 'C';
+   
+   /** Value of recordType field in a multi-TM tx committed record */
+   static final byte MULTI_TM_TX_COMMITTED = (byte) 'M';
+
+   /** Value of recordType field in a tx prepared record */
+   static final byte TX_PREPARED = (byte) 'P';
+
+   /** Value of recordType field in a JCA tx prepared record */
+   static final byte JCA_TX_PREPARED = (byte) 'R';
+
+   /** Value of recordType field in a tx end record */
+   static final byte TX_END = (byte) 'E';
+
+   /** Value of recordType field in a heuristic status record */
+   static final byte HEUR_STATUS = (byte) 'H';
+
+   /** Value of recordType field in a heuristic status record */
+   static final byte HEUR_FORGOTTEN = (byte) 'F';
+
+   /** Size of varField directory entry */
+   private static final int SIZEOF_DIR_ENTRY =
+         SIZEOF_SHORT    /* offset */
+         + SIZEOF_SHORT; /* length   */
+   
+   /** 
+    * Minimum length of a multi-TM tx committed 
+    * (<code>MULTI_TM_TX_COMMITTED</code>) record. 
+    */
+   private static final int MIN_MULTI_TM_TX_COMMITTED_LEN = 
+         HEADER_LEN      /* magic header                    */
+         + SIZEOF_SHORT  /* record length                   */
+         + SIZEOF_SHORT  /* record length check             */
+         + SIZEOF_BYTE   /* record type                     */ 
+         + SIZEOF_LONG   /* local transaction id            */ 
+         + SIZEOF_SHORT  /* count of dir entries            */
+         + CHKSUM_LEN;   /* checksum                        */ 
+   
+   /** 
+    * Minimum length of a tx prepared (<code>TX_PREPARED</code>) 
+    * or JCA tx prepared (<code>JCA_TX_PREPARED</code>) record.
+    */
+   private static final int MIN_TX_PREPARED_LEN = 
+         HEADER_LEN      /* magic header                    */
+         + SIZEOF_SHORT  /* record length                   */
+         + SIZEOF_SHORT  /* record length check             */
+         + SIZEOF_BYTE   /* record type                     */ 
+         + SIZEOF_LONG   /* local transaction id            */ 
+         + SIZEOF_SHORT  /* count of dir entries            */
+         + FORMAT_ID_LEN /* inbound format id               */
+         + SIZEOF_SHORT  /* length of global transaction id */
+         + CHKSUM_LEN;   /* checksum                        */ 
+   
+   /**
+    * Fixed length of a single-TM tx committed (<code>TX_COMMITTED</code>) 
+    * record. 
+    */
+   private static final int TX_COMMITED_LEN = 
+      HEADER_LEN      /* magic header               */
+      + SIZEOF_SHORT  /* record length              */
+      + SIZEOF_SHORT  /* record length check        */
+      + SIZEOF_BYTE   /* record type (TX_COMMITTED) */ 
+      + SIZEOF_LONG   /* local transaction id       */ 
+      + CHKSUM_LEN;   /* checksum                   */ 
+
+   /** 
+    * Fixed length of a tx end (<code>TX_END</code>) record.
+    */
+   static final int TX_END_LEN = 
+      HEADER_LEN      /* magic header         */
+      + SIZEOF_SHORT  /* record length        */
+      + SIZEOF_SHORT  /* record length check  */
+      + SIZEOF_BYTE   /* record type (TX_END) */ 
+      + SIZEOF_LONG   /* local transaction id */
+      + CHKSUM_LEN;   /* checksum             */ 
+
+   /** 
+    * Minimum length of a heuristic status (<code>HEUR_STATUS</code>) record. 
+    */
+   private static final int MIN_HEUR_STATUS_LEN = 
+         HEADER_LEN         /* magic header                                   */
+         + SIZEOF_SHORT     /* record length                                  */
+         + SIZEOF_SHORT     /* record length check                            */
+         + SIZEOF_BYTE      /* record type (HEUR_STATUS)                      */ 
+         + SIZEOF_LONG      /* local transaction id                           */
+         + SIZEOF_SHORT     /* count of dir entries                           */
+         + SIZEOF_BYTE      /* transaction status                             */
+         + SIZEOF_BYTE      /* heuristic status code                          */
+         + SIZEOF_BYTE      /* locally-detected heuristic hazard flag         */
+         + SIZEOF_BYTE      /* foreign tx flag                                */
+         + FORMAT_ID_LEN    /* format id                                      */
+         + SIZEOF_SHORT     /* length of global transaction id                */
+         + SIZEOF_DIR_ENTRY /* varField with inbound branch qualifier         */ 
+         + SIZEOF_DIR_ENTRY /* varField with heuristic codes for XA resources */ 
+         + CHKSUM_LEN;      /* checksum                                       */ 
+   
+   /** 
+    * Fixed length of a heuristic forgotten (HEUR_FORGOTTEN) record.
+    */
+   static final int HEUR_FORGOTTEN_LEN = 
+      HEADER_LEN      /* magic header                 */
+      + SIZEOF_SHORT  /* record length                */
+      + SIZEOF_SHORT  /* record length check          */
+      + SIZEOF_BYTE   /* record type (HEUR_FORGOTTEN) */ 
+      + SIZEOF_LONG   /* local transaction id         */
+      + CHKSUM_LEN;   /* checksum                     */ 
+
+   
+
+   /**
+    * Structure filled out by the method <code>LogRecord.getData()</code>.
+    */
+   public static class Data
+   {
+      public byte recordType;
+      public long localTransactionId;
+      public byte[] globalTransactionId;
+      public int inboundFormatId;
+      public String recoveryCoordinator;
+      public byte[] inboundBranchQualifier;
+      public String[] resources;      
+   }
+   
+   /**
+    * Structure filled out by the method <code>LogRecord.getHeurData()</code>.
+    */
+   public static class HeurData
+   {
+      public byte recordType;
+      public long localTransactionId;
+      public boolean foreignTx;
+      public int formatId;
+      public byte[] globalTransactionId;
+      public byte[] inboundBranchQualifier;
+      public int transactionStatus;
+      public int heuristicStatusCode;
+      public boolean locallyDetectedHeuristicHazard;
+      public int[] xaResourceHeuristics;
+      public HeuristicStatus[] remoteResourceHeuristics;
+   }
+   
+   /**
+    * Private constructor to enforce non-instantiability.
+    */    
+   private LogRecord()
+   {
+   }
+      
+   /**
+    * Creates a tx committed record for a locally-started transaction that 
+    * does not involve other transaction managers.
+    *  
+    * @param localTransactionId the local id of the transaction
+    * @return a <code>ByteBuffer</code> containing the tx committed record. 
+    *         The buffer position is set to zero and the buffer limit is set 
+    *         to the number of bytes in the commit record.
+    */
+   static ByteBuffer createTxCommittedRecord(long localTransactionId)
+   {
+      ByteBuffer buffer = ByteBuffer.allocate(TX_COMMITED_LEN);
+      
+      buffer.put(HEADER)
+            .putShort((short) (TX_COMMITED_LEN - FULL_HEADER_LEN))
+            .putShort((short) -(TX_COMMITED_LEN - FULL_HEADER_LEN))
+            .put(TX_COMMITTED)
+            .putLong(localTransactionId);
+      
+      Checksum checksum = new Adler32();
+      checksum.update(buffer.array(),  
+                      FULL_HEADER_LEN, 
+                      SIZEOF_BYTE + SIZEOF_LONG);
+      buffer.putInt(TX_COMMITED_LEN - CHKSUM_LEN, (int) checksum.getValue());
+
+      return (ByteBuffer) buffer.position(0);
+   }
+   
+   /**
+    * Creates a multi-TM tx committed record for a distributed transaction.
+    * 
+    * @param localTransactionId the local id of the transaction
+    * @param resources an array of stringfied references for the remote
+    *                  resources (<code>org.jboss.tm.remoting.Resource</code>
+    *                  instances) enlisted in the transaction.
+    * @return a <code>ByteBuffer</code> containing the tx committed record. 
+    *         The buffer position is set to zero and the buffer limit is set 
+    *         to the number of bytes in the record.
+    */
+   static ByteBuffer createTxCommittedRecord(long localTransactionId,
+                                             String[] resources)
+   {
+      int recordLen = MIN_MULTI_TM_TX_COMMITTED_LEN;
+      int resourceCount = 0;
+
+      if (resources != null && (resourceCount = resources.length) > 0)
+      {
+         for (int i = 0; i < resourceCount; i++)
+         {
+            recordLen += SIZEOF_DIR_ENTRY        /* offset and length */ 
+                        + resources[i].length(); /* data                */
+         }
+      }
+      else
+         throw new RuntimeException("No remote resources were specified");
+
+      ByteBuffer buffer = ByteBuffer.allocate(recordLen);
+      
+      buffer.put(HEADER)
+            .putShort((short) (recordLen - FULL_HEADER_LEN))
+            .putShort((short) -(recordLen - FULL_HEADER_LEN))
+            .put(MULTI_TM_TX_COMMITTED)
+            .putLong(localTransactionId)
+            .putShort((short) resourceCount);
+      
+      for (int i= 0; i < resourceCount; i++)
+      {
+         int offset = buffer.position();
+         int length = resources[i].length();
+         byte[] resourceBytes = new byte[length];
+         
+         resources[i].getBytes(0, length, resourceBytes, 0);
+         buffer.put(resourceBytes)
+               .putShort(recordLen 
+                         - CHKSUM_LEN - SIZEOF_SHORT - SIZEOF_DIR_ENTRY * i, 
+                         (short) length)
+               .putShort(recordLen 
+                         - CHKSUM_LEN - SIZEOF_DIR_ENTRY - SIZEOF_DIR_ENTRY * i, 
+                         (short) offset);
+      }
+      
+      Checksum checksum = new Adler32();
+      checksum.update(buffer.array(), 
+                      FULL_HEADER_LEN, 
+                      recordLen - FULL_HEADER_LEN - CHKSUM_LEN);
+      buffer.putInt(recordLen - CHKSUM_LEN, (int) checksum.getValue());
+      
+      return (ByteBuffer) buffer.position(0);
+   }
+
+   /**
+    * Creates a tx prepared record or a JCA tx prepared record.
+    * 
+    * @param localTransactionId the local id of the transaction
+    * @param inboundFormatId the format id of the foreign <code>Xid</code>
+    * @param globalTransactionId the global id of the transaction
+    * @param jcaInboundTransaction true if this method should create a JCA tx 
+    *                              prepared record, false if this method should
+    *                              create a tx prepared record
+    * @param recoveryCoordOrInboundBranchQual an stringfied recovery 
+    *                  coordinator converted to a byte array (if 
+    *                  jcaInboundTransaction is false) or the inbound 
+    *                  branch qualifier of a JCA inbound transaction (if 
+    *                  jcaInboundTransaction is true) 
+    * @param resources an array of stringfied references for the remote
+    *                  resources (<code>org.jboss.tm.remoting.Resource</code>
+    *                  instances) enlisted in the transaction.
+    * @return a <code>ByteBuffer</code> containing the tx prepared record or
+    *         JCA tx prepared record. The buffer position is set to zero and 
+    *         the buffer limit is set to the number of bytes in the record.
+    */
+   private static ByteBuffer createTxPreparedRecord(
+                                       long localTransactionId,
+                                       int inboundFormatId,
+                                       byte[] globalTransactionId,
+                                       boolean jcaInboundTransaction,
+                                       byte[] recoveryCoordOrInboundBranchQual,
+                                       String[] resources)
+   {
+      int recordLen = MIN_TX_PREPARED_LEN;
+      int globalTxIdLen = 0;
+      int resourceCount = 0;
+
+      if (globalTransactionId != null &&
+            (globalTxIdLen = globalTransactionId.length) > 0)
+      {
+         recordLen += globalTxIdLen; 
+      }
+      else
+         throw new RuntimeException("The global transaction id " + 
+                                    "was not specified");
+         
+      if (resources != null && (resourceCount = resources.length) > 0)
+      {
+         for (int i = 0; i < resourceCount; i++)
+         {
+            recordLen += SIZEOF_DIR_ENTRY        /* dir entry */ 
+                        + resources[i].length(); /* data      */
+         }
+      }
+
+      recordLen += SIZEOF_DIR_ENTRY                          /* dir entry */ 
+                  + recoveryCoordOrInboundBranchQual.length; /* data      */
+
+      ByteBuffer buffer = ByteBuffer.allocate(recordLen);
+
+      buffer.put(HEADER)
+            .putShort((short) (recordLen - FULL_HEADER_LEN))
+            .putShort((short) -(recordLen - FULL_HEADER_LEN))
+            .put(jcaInboundTransaction ? JCA_TX_PREPARED : TX_PREPARED)
+            .putLong(localTransactionId)
+            .putShort((short) (resourceCount + 1))
+            .putInt(inboundFormatId)
+            .putShort((short) globalTxIdLen)
+            .put(globalTransactionId);
+
+      int offset = buffer.position();
+      int length = recoveryCoordOrInboundBranchQual.length;
+
+      buffer.put(recoveryCoordOrInboundBranchQual)
+            .putShort(recordLen - CHKSUM_LEN - SIZEOF_SHORT, (short) length)
+            .putShort(recordLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY, 
+                      (short) offset);
+
+      for (int i = 0; i < resourceCount; )
+      {
+         offset = buffer.position();
+         length = resources[i].length();
+         byte[] byteArray = new byte[length];
+
+         resources[i].getBytes(0, length, byteArray, 0);
+         i++; // increment i *before* storing the entry (because entry 0 is 
+              // the recovery coordinator or inbound branch qualifier) 
+         buffer.put(byteArray)
+               .putShort(recordLen 
+                         - CHKSUM_LEN - SIZEOF_SHORT - SIZEOF_DIR_ENTRY * i, 
+                         (short) length)
+               .putShort(recordLen 
+                         - CHKSUM_LEN - SIZEOF_DIR_ENTRY - SIZEOF_DIR_ENTRY * i, 
+                         (short) offset);
+      }
+
+      Checksum checksum = new Adler32();
+      checksum.update(buffer.array(), 
+                      FULL_HEADER_LEN, 
+                      recordLen - FULL_HEADER_LEN - CHKSUM_LEN);
+      buffer.putInt(recordLen - CHKSUM_LEN, (int) checksum.getValue());
+
+      return (ByteBuffer) buffer.position(0);
+   }
+
+   /**
+    * Creates a tx prepared record for a distributed transaction.
+    * 
+    * @param localTransactionId the local id of the transaction
+    * @param inboundFormatId the format id of the foreign <code>Xid</code>
+    * @param globalTransactionId the global id of the transaction
+    * @param recoveryCoordinator a stringfied reference for the remote
+    *                  coordinator, an 
+    *                  <code>org.jboss.tm.remoting.RecoveryCoordinator</code>
+    *                  instance
+    * @param resources an array of stringfied references for the remote
+    *                  resources (<code>org.jboss.tm.remoting.Resource</code>
+    *                  instances) enlisted in the transaction.
+    * @return a <code>ByteBuffer</code> containing the tx prepared record. 
+    *         The buffer position is set to zero and the buffer limit is set 
+    *         to the number of bytes in the record.
+    */
+   static ByteBuffer createTxPreparedRecord(long localTransactionId,
+                                            int inboundFormatId,         
+                                            byte[] globalTransactionId,
+                                            String recoveryCoordinator,
+                                            String[] resources)
+   {
+      int len = recoveryCoordinator.length();
+      byte[] coordinatorByteArray = new byte[len];
+
+      recoveryCoordinator.getBytes(0, len, coordinatorByteArray, 0);
+      
+      return createTxPreparedRecord(localTransactionId,
+                                    inboundFormatId,
+                                    globalTransactionId,
+                                    false /* not JCA inbound */,
+                                    coordinatorByteArray,
+                                    resources);
+   }
+   
+   /**
+    * Creates a tx prepared record for a JCA inbound transaction.
+    * 
+    * @param localTransactionId the local id of the transaction
+    * @param inboundXid a foreign <code>Xid</code> instance
+    * @param resources an array of stringfied references for the remote
+    *                  resources (<code>org.jboss.tm.remoting.Resource</code>
+    *                  instances) enlisted in the transaction.
+    * @return a <code>ByteBuffer</code> containing the JCA tx prepared record.
+    *         The buffer position is set to zero and the buffer limit is set 
+    *         to the number of bytes in the record.
+    */
+   static ByteBuffer createJcaTxPreparedRecord(long localTransactionId,
+                                               Xid inboundXid,
+                                               String[] resources)
+   {
+      return createTxPreparedRecord(localTransactionId,
+                                    inboundXid.getFormatId(),
+                                    inboundXid.getGlobalTransactionId(),
+                                    true /* JCA inbound */,
+                                    inboundXid.getBranchQualifier(),
+                                    resources);
+   }
+
+   /**
+    * Creates a tx end record for a distributed transaction.
+    * 
+    * @param localTransactionId the local id of the transaction.
+    * @return a <code>ByteBuffer</code> containing the end record. The
+    *         buffer position is set to zero and the buffer limit is set to 
+    *         the number of bytes in the end record.
+    */
+   static ByteBuffer createTxEndRecord(long localTransactionId)
+   {
+      ByteBuffer buffer = ByteBuffer.allocate(TX_END_LEN);
+      
+      buffer.put(HEADER)
+            .putShort((short) (TX_END_LEN - FULL_HEADER_LEN))
+            .putShort((short) -(TX_END_LEN - FULL_HEADER_LEN))
+            .put(TX_END)
+            .putLong(localTransactionId);
+      
+      Checksum checksum = new Adler32();
+      checksum.update(buffer.array(), 
+                      FULL_HEADER_LEN, 
+                      SIZEOF_BYTE + SIZEOF_LONG);
+      buffer.putInt(TX_END_LEN - CHKSUM_LEN, (int) checksum.getValue());
+
+      return (ByteBuffer) buffer.position(0);
+   }
+
+   /**
+    * Creates a heuristic status record for a transaction.
+    * 
+    * @param localTransactionId the local id of the transaction
+    * @param foreignTx true if the transaction is a foreign one, false otherwise
+    * @param formatId the format id field of the transaction's <code>Xid</code>
+    * @param globalTransactionId the global id field of the transaction's 
+    *            <code>Xid</code>
+    * @param inboundBranchQualifier the inbound branch qualifier, in the case 
+    *            of a foreign transaction that has been imported via JCA 
+    *            transaction inflow, or null otherwise   
+    * @param transactionStatus the transaction status 
+    *             (<code>javax.transaction.Status.STATUS_COMMITTING</code>, or
+    *             <code>javax.transaction.Status.STATUS_COMMITTED</code>, or
+    *             <code>javax.transaction.Status.STATUS_ROLLING_BACK</code>, or
+    *             <code>javax.transaction.Status.STATUS_ROLLEDBACK</code>)
+    * @param heurStatusCode the heuristic status code, which takes the same
+    *             values as the <code>errorCode</code> field of 
+    *             <code>javax.transaction.xa.XAException</code>
+    * @param locallyDetectedHeuristicHazard true if a heuristic hazard was 
+    *             detected locally and is still outstanding
+    * @param xaResourceHeuristics array with the heuristic status codes of
+    *                  the XA resources that reported heuristic decisions,
+    *                  or null if no XA resources reported heuristic decisions
+    * @param remoteResourceHeuristics array with the heuristic status of
+    *                  the remote resources that reported heuristic decisions,
+    *                  or null if no remote resources reported heuristic 
+    *                  decisions
+    * @return a <code>ByteBuffer</code> containing the heuristic status record.
+    *         The buffer position is set to zero and the buffer limit is set 
+    *         to the number of bytes in the record.
+    */
+   static ByteBuffer createHeurStatusRecord(
+                                    long localTransactionId,
+                                    boolean foreignTx,
+                                    int formatId, 
+                                    byte[] globalTransactionId,
+                                    byte[] inboundBranchQualifier,
+                                    int transactionStatus,
+                                    int heurStatusCode,
+                                    boolean locallyDetectedHeuristicHazard,
+                                    int[] xaResourceHeuristics, 
+                                    HeuristicStatus[] remoteResourceHeuristics)
+   {
+      int recordLen = MIN_HEUR_STATUS_LEN;
+      int globalTxIdLen = 0;
+      int remoteResourceHeuristicsCount = 0;
+      
+      if (globalTransactionId != null)
+         recordLen += (globalTxIdLen = globalTransactionId.length); 
+      if (inboundBranchQualifier != null)
+         recordLen += inboundBranchQualifier.length;
+      if (xaResourceHeuristics != null)
+         recordLen += xaResourceHeuristics.length;
+      if (remoteResourceHeuristics != null)
+      {
+         remoteResourceHeuristicsCount = remoteResourceHeuristics.length;
+         for (int i = 0; i < remoteResourceHeuristicsCount; i++)
+         {
+            recordLen += SIZEOF_DIR_ENTRY        /* offset and length */
+                        + SIZEOF_BYTE            /* data */ 
+                        + remoteResourceHeuristics[i].resourceRef.length();
+         }
+      }
+
+      ByteBuffer buffer = ByteBuffer.allocate(recordLen);
+      
+      buffer.put(HEADER)
+            .putShort((short) (recordLen - FULL_HEADER_LEN))
+            .putShort((short) -(recordLen - FULL_HEADER_LEN))
+            .put(HEUR_STATUS)
+            .putLong(localTransactionId)
+            .putShort((short) (remoteResourceHeuristicsCount + 2))
+            .put((byte) transactionStatus)
+            .put((byte) heurStatusCode)
+            .put((locallyDetectedHeuristicHazard) ? (byte) 1 : (byte) 0)
+            .put((foreignTx) ? (byte) 1 : (byte) 0)
+            .putInt(formatId)
+            .putShort((short) globalTxIdLen)
+            .put(globalTransactionId);
+
+      
+      int offset, length;
+      
+      // varField 0: inboundBranchQualifier
+      offset = buffer.position();
+      length = (inboundBranchQualifier == null) ? 0 
+                                                : inboundBranchQualifier.length;
+      if (length > 0)
+         buffer.put(inboundBranchQualifier);
+      buffer.putShort(recordLen - CHKSUM_LEN - SIZEOF_SHORT, (short) length)
+            .putShort(recordLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY, (short) offset);
+      
+      // varField 1: xaResourceHeuristics
+      offset = buffer.position();
+      length = (xaResourceHeuristics == null) ? 0
+                                                : xaResourceHeuristics.length;
+      if (length > 0)
+      {
+         byte[] xaResHeurCodes = new byte[length];
+         for (int i = 0; i < length; i++)
+            xaResHeurCodes[i] = (byte) xaResourceHeuristics[i];
+         buffer.put(xaResHeurCodes);
+      }
+      buffer.putShort(recordLen - CHKSUM_LEN 
+                                - SIZEOF_SHORT - SIZEOF_DIR_ENTRY, 
+                      (short) length)
+            .putShort(recordLen - CHKSUM_LEN 
+                                - SIZEOF_DIR_ENTRY - SIZEOF_DIR_ENTRY, 
+                      (short) offset);
+      
+      // varField 2, ... : remoteResourceHeuristics
+      for (int i = 0 ; i < remoteResourceHeuristicsCount; i++)
+      {
+         String resourceRef = remoteResourceHeuristics[i].resourceRef;
+         offset = buffer.position();
+         length = resourceRef.length() + 1;
+         byte[] heurStatus = new byte[length];
+         heurStatus[0] = (byte) remoteResourceHeuristics[i].code;
+         resourceRef.getBytes(0, resourceRef.length(), heurStatus, 1);
+         buffer.put(heurStatus)
+               .putShort(recordLen - CHKSUM_LEN 
+                                   - SIZEOF_SHORT - SIZEOF_DIR_ENTRY * (i + 2), 
+                         (short) length)
+               .putShort(recordLen - CHKSUM_LEN 
+                                   - SIZEOF_DIR_ENTRY - SIZEOF_DIR_ENTRY * (i + 2), 
+                         (short) offset);
+      }
+
+      Checksum checksum = new Adler32();
+      checksum.update(buffer.array(), 
+                      FULL_HEADER_LEN, 
+                      recordLen - FULL_HEADER_LEN - CHKSUM_LEN);
+      buffer.putInt(recordLen - CHKSUM_LEN, (int) checksum.getValue());
+      
+      return (ByteBuffer) buffer.position(0);
+   }
+   
+   /**
+    * Creates a heuristic forgotten record for a transaction.
+    * 
+    * @param localTransactionId the local id of the transaction.
+    * @return a <code>ByteBuffer</code> containing the heuristic forgotten 
+    *         record. The buffer position is set to zero and the buffer limit 
+    *         is set to the number of bytes in the heuristic forgotten record.
+    */
+   static ByteBuffer createHeurForgottenRecord(long localTransactionId)
+   {
+      ByteBuffer buffer = ByteBuffer.allocate(HEUR_FORGOTTEN_LEN);
+      
+      buffer.put(HEADER)
+            .putShort((short) (HEUR_FORGOTTEN_LEN - FULL_HEADER_LEN))
+            .putShort((short) -(HEUR_FORGOTTEN_LEN - FULL_HEADER_LEN))
+            .put(HEUR_FORGOTTEN)
+            .putLong(localTransactionId);
+      
+      Checksum checksum = new Adler32();
+      checksum.update(buffer.array(), 
+                      FULL_HEADER_LEN, 
+                      SIZEOF_BYTE + SIZEOF_LONG);
+      buffer.putInt(TX_END_LEN - CHKSUM_LEN, (int) checksum.getValue());
+
+      return (ByteBuffer) buffer.position(0);
+   }
+   
+   /**
+    * Fills out a <code>Data</code> structure with the information taken from
+    * the log record in a given <code>ByteBuffer</code>. The log record starts
+    * at the beginning of the buffer. It cannot be a log record with heuristic
+    * information (i.e., its type cannot be <code>HEUR_STATUS</code> or 
+    * <code>HEUR_FORGOTTEN</code>). Its length is known in advance and may
+    * be smaller than the number of bytes in the buffer. The magic header and
+    * record length fields are not included in the <code>ByteBuffer</code>,
+    * whose first byte is the record type of the log record.
+    * 
+    * @param buffer a <code>ByteBuffer</code> containing the part of a log 
+    *               record that starts at the record type field (which is the
+    *               first byte of the <code>ByteBuffer</code> and goes until
+    *               the checksum field (which is included in the 
+    *               <code>ByteBuffer</code> 
+    * @param recLen the length of the log record in the buffer
+    * @param data a <code>Data</code> structure to be filled out with the
+    *               information extracted from the log record.
+    */
+   static void getData(ByteBuffer buffer, int recLen, Data data)
+   {
+      short gidLength;
+      short countOfDirEntries;
+      
+      if (recLen > buffer.limit())
+         return; // TODO: throw an exception in this case
+      
+      int checksumField = buffer.getInt(recLen - CHKSUM_LEN);
+
+      Checksum checksum = new Adler32();
+      checksum.update(buffer.array(), 0, recLen - CHKSUM_LEN);
+      
+      if ((int) checksum.getValue() != checksumField)
+      {
+         throw new CorruptedLogRecordException("Wrong checksum.");
+      }
+
+      data.recordType = buffer.get();
+      
+      switch (data.recordType)
+      {
+      case MULTI_TM_TX_COMMITTED:
+         data.localTransactionId = buffer.getLong();
+         countOfDirEntries = buffer.getShort();
+
+         // Get varField 0, ..., varField N (where N = countOfDirEntries - 1)
+         data.resources = new String[countOfDirEntries]; 
+         for (int i = 0; i < countOfDirEntries; i++)
+         {
+            short length = buffer.getShort(recLen - CHKSUM_LEN 
+                                                  - SIZEOF_SHORT 
+                                                  - SIZEOF_DIR_ENTRY * i);
+            short offset = buffer.getShort(recLen - CHKSUM_LEN 
+                                                  - SIZEOF_DIR_ENTRY 
+                                                  - SIZEOF_DIR_ENTRY * i);
+            offset -= FULL_HEADER_LEN; // Our buffer starts after the header! 
+            data.resources[i] = new String(buffer.array(), 0, offset, length);
+         }
+
+         // Nullify unused data fields
+         data.globalTransactionId = null;
+         data.inboundFormatId = -1;
+         data.recoveryCoordinator = null;
+         data.inboundBranchQualifier = null;
+         break;
+         
+      case TX_PREPARED:
+      case JCA_TX_PREPARED:
+         data.localTransactionId = buffer.getLong();
+         countOfDirEntries = buffer.getShort();
+         data.inboundFormatId = buffer.getInt();
+         gidLength = buffer.getShort();
+         data.globalTransactionId = new byte[gidLength];
+         buffer.get(data.globalTransactionId);
+         
+         // Get lentgh and offset of varField 0:
+         short length = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_SHORT); 
+         short offset = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY);
+         offset -= FULL_HEADER_LEN; // Our buffer starts after the header!
+         // (Alternatively we could have set offset to buffer.position(), 
+         //  as varField 0 cames immediately after the globalTransactionId.)
+
+         if (data.recordType == TX_PREPARED)
+         {
+            // varField 0 is the recovery coordinator 
+            data.recoveryCoordinator = 
+               new String(buffer.array(), 0, offset, length);
+            data.inboundBranchQualifier = null;
+         }
+         else
+         {
+            // varField 0 is the inbound branch qualifier
+            data.recoveryCoordinator = null;
+            data.inboundBranchQualifier = new byte[length];
+            // At this point (offset == buffer.position),
+            // so the line below is commented out:
+            // buffer.position(offset);
+            buffer.get(data.inboundBranchQualifier);
+         }
+         
+         // Get varField 1, ... varField N (where N == countOfDirEntries - 1)  
+         data.resources = new String[countOfDirEntries - 1]; 
+         for (int i = 1; i < countOfDirEntries; i++)
+         {
+            length = buffer.getShort(recLen - CHKSUM_LEN 
+                                            - SIZEOF_SHORT 
+                                            - SIZEOF_DIR_ENTRY * i);
+            offset = buffer.getShort(recLen - CHKSUM_LEN 
+                                            - SIZEOF_DIR_ENTRY 
+                                            - SIZEOF_DIR_ENTRY * i);
+            offset -= FULL_HEADER_LEN; // Our buffer starts after the header!
+            data.resources[i - 1] = 
+               new String(buffer.array(), 0, offset, length);
+         }
+         break;
+         
+      case TX_COMMITTED:
+      case TX_END:
+         data.localTransactionId = buffer.getLong();
+         // Nullify the remaining data fields
+         data.globalTransactionId = null;
+         data.inboundFormatId = -1;
+         data.recoveryCoordinator = null;
+         data.inboundBranchQualifier = null;
+         data.resources = null;
+         break;
+         
+      case HEUR_STATUS: 
+      case HEUR_FORGOTTEN:
+         throw new RuntimeException("Log record with unexpected type");
+         
+      default:
+         throw new RuntimeException("Log record with invalid type");
+      }
+   }
+
+   /**
+    * Fills out a <code>HeurData</code> structure with the information taken 
+    * from the <code>HEUR_STATUS</code> or <code>HEUR_FORGOTTEN</code> log 
+    * record in a given <code>ByteBuffer</code>. The log record starts at the 
+    * beginning of the buffer. Its length is known in advance and may be 
+    * smaller than the number of bytes in the buffer. The magic header and 
+    * record length fields are not included in the <code>ByteBuffer</code>,
+    * whose first byte is the record type of the log record.
+    * 
+    * @param buffer a <code>ByteBuffer</code> containing the part of a 
+    *               <code>HEUR_STATUS</code> or <code>HEUR_FORGOTTEN</code> log 
+    *               record that starts at the record type field (which is the
+    *               first byte of the <code>ByteBuffer</code> and goes until
+    *               the checksum field (which is included in the 
+    *               <code>ByteBuffer</code> 
+    * @param recLen the length of the log record in the buffer
+    * @param data a <code>HeurData</code> structure to be filled out with the
+    *               information extracted from the log record.
+    */
+   static void getHeurData(ByteBuffer buffer, int recLen, HeurData data)
+   {
+      if (recLen > buffer.limit())
+         return; // TODO: throw an exception in this case
+      
+      int checksumField = buffer.getInt(recLen - CHKSUM_LEN);
+
+      Checksum checksum = new Adler32();
+      checksum.update(buffer.array(), 0, recLen - CHKSUM_LEN);
+      
+      if ((int) checksum.getValue() != checksumField)
+      {
+         throw new CorruptedLogRecordException("Wrong checksum.");
+      }
+
+      data.recordType = buffer.get();
+      
+      switch (data.recordType)
+      {
+      case HEUR_STATUS: 
+         data.localTransactionId = buffer.getLong();
+         short countOfDirEntries = buffer.getShort();
+         data.transactionStatus = buffer.get();
+         data.heuristicStatusCode = buffer.get();
+         data.locallyDetectedHeuristicHazard = (buffer.get() != 0);
+         data.foreignTx = (buffer.get() != 0);
+         data.formatId = buffer.getInt();
+         int gidLength = buffer.getShort();
+         data.globalTransactionId = new byte[gidLength];
+         buffer.get(data.globalTransactionId);
+         
+         short length, offset;
+         
+         // Get lentgh and offset of varField 0:
+         length = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_SHORT); 
+         offset = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY);
+         offset -= FULL_HEADER_LEN; // Our buffer starts after the header!
+         // (Alternatively we could have set offset to buffer.position(), 
+         //  as varField 0 cames immediately after the globalTransactionId.)
+
+         if (length == 0)
+            data.inboundBranchQualifier = null;
+         else
+         {
+            data.inboundBranchQualifier = new byte[length];
+            // At this point (offset == buffer.position),
+            // so the line below is commented out:
+            // buffer.position(offset);
+            buffer.get(data.inboundBranchQualifier);
+         }
+         
+         // Get lentgh and offset of varField 1:
+         length = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_SHORT - SIZEOF_DIR_ENTRY); 
+         offset = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY - SIZEOF_DIR_ENTRY);
+         offset -= FULL_HEADER_LEN; // Our buffer starts after the header!
+
+         if (length == 0)
+            data.xaResourceHeuristics = null;
+         else
+         {
+            byte[] xaResHeurCodes = new byte[length];
+            buffer.position(offset);
+            buffer.get(xaResHeurCodes);
+            data.xaResourceHeuristics = new int[length];
+            for (int i = 0; i < length; i++)
+               data.xaResourceHeuristics[i] = xaResHeurCodes[i]; 
+         }
+         
+         if (countOfDirEntries > 2)
+            data.remoteResourceHeuristics = 
+               new HeuristicStatus[countOfDirEntries - 2];
+         else
+            data.remoteResourceHeuristics = null;
+         
+         // Get varField 2, ... varField N (where N == countOfDirEntries - 1)
+         for (int i = 2; i < countOfDirEntries; i++)
+         {
+            length = buffer.getShort(recLen - CHKSUM_LEN 
+                                            - SIZEOF_SHORT 
+                                            - SIZEOF_DIR_ENTRY * i);
+            offset = buffer.getShort(recLen - CHKSUM_LEN 
+                                            - SIZEOF_DIR_ENTRY 
+                                            - SIZEOF_DIR_ENTRY * i);
+            offset -= FULL_HEADER_LEN; // Our buffer starts after the header!
+            
+            byte remoteResourceHeurCode = buffer.get(offset);
+            String remoteResourceRef = 
+               new String(buffer.array(), 0, offset + 1, length - 1); 
+            data.remoteResourceHeuristics[i - 2] =
+               new HeuristicStatus(remoteResourceHeurCode, remoteResourceRef);
+         }
+         break;
+         
+      case HEUR_FORGOTTEN:
+         data.localTransactionId = buffer.getLong();
+         data.heuristicStatusCode = 0;
+         data.xaResourceHeuristics = null;
+         data.remoteResourceHeuristics = null;
+         break;
+         
+      case MULTI_TM_TX_COMMITTED:
+      case TX_PREPARED:
+      case JCA_TX_PREPARED:
+      case TX_COMMITTED:
+      case TX_END:
+         throw new RuntimeException("Log record with unexpected type");
+         
+      default:
+         throw new RuntimeException("Log record with invalid type");
+         
+      }      
+   }
+   
+   /**
+    * Gets the lenght of the log record that follows the one at the beginning
+    * of a given buffer. This method assumes that the header, record lenght,
+    * and record length check of the next record follows the current record 
+    * in the buffer.
+    *   
+    * @param buffer a <code>ByteBuffer</code> containing the header, record
+    *               lenght, and record length check of the next log record
+    * @param currentRecordLen the buffer position at which the header starts.
+    * @return the next record length, a short value read from the absolute
+    *         buffer position <code>currentRecordLength + HEADER_LEN</code> 
+    *                      
+    */
+   static int getNextRecordLength(ByteBuffer buffer, int currentRecordLength)
+   {
+      buffer.position(currentRecordLength);
+      if (buffer.remaining() < FULL_HEADER_LEN)
+         return 0;
+      else 
+      {
+         byte[] header = new byte[HEADER_LEN];
+         buffer.get(header);
+         if (!Arrays.equals(header, HEADER))
+         {
+            if (Arrays.equals(header, NULL_HEADER))
+               return 0;
+            else 
+               throw new CorruptedLogRecordException("Invalid header.");
+         }
+         else
+         {
+            short recLen = buffer.getShort();
+            short recLenCheck = buffer.getShort();
+            if (recLenCheck != -recLen)
+               throw new CorruptedLogRecordException("Record lenght check failed.");
+            return recLen;
+            
+         }
+      }
+   }
+   
+   /**
+    * Receives a byte array containing the part of a log record that follows
+    * the header and verifies if the record has a valid checksum.
+    *  
+    * @param buf a byte array containing the part of a log record that starts
+    *            with the record type and ends with the checksum, which is 
+    *            included in the array.
+    * @return true if the checksum is valid, false otherwise.
+    */
+   static boolean hasValidChecksum(byte[] buf)
+   {
+      int bufLen = buf.length;
+      int checksumField = ByteBuffer.wrap(buf).getInt(bufLen - CHKSUM_LEN);
+      Checksum checksum = new Adler32();
+      checksum.update(buf, 0, bufLen - CHKSUM_LEN);
+      return ((int) checksum.getValue() == checksumField);
+   }
+
+   /**
+    * Returs the string representation of the log record in a buffer. 
+    * 
+    * @param buffer a <code>ByteBuffer</code> containing a log record, 
+    *               with the buffer position set to zero and the buffer limit
+    *               set to the number of bytes in the commit record.
+    * @return a string that describes the log record.
+    */
+   static String toString(ByteBuffer buffer)
+   {
+      short countOfDirEntries;
+      short offset;
+      short length;
+      short gidLen;
+      byte[] gid;
+      
+      int recLen = buffer.limit();
+      buffer.position(FULL_HEADER_LEN);
+      
+      StringBuffer sb = new StringBuffer("Record Info:\n    Type: ");
+      byte recordType = buffer.get();
+      
+      switch (recordType)
+      {
+         case MULTI_TM_TX_COMMITTED:
+            sb.append("MULTI_TM_TX_COMMITTED\n");
+            sb.append("    Local transaction id: ");
+            sb.append(buffer.getLong());
+            sb.append("\n");
+            countOfDirEntries = buffer.getShort();
+            if (countOfDirEntries > 0)
+            {               
+               sb.append("    Resources:\n");
+               // Get varField 0, ..., varField N (where N = countOfDirEntries - 1)
+               for (int i = 0; i < countOfDirEntries; i++)
+               {
+                  length = buffer.getShort(recLen - CHKSUM_LEN 
+                                                  - SIZEOF_SHORT 
+                                                  - SIZEOF_DIR_ENTRY * i);
+                  offset = buffer.getShort(recLen - CHKSUM_LEN 
+                                                  - SIZEOF_DIR_ENTRY 
+                                                  - SIZEOF_DIR_ENTRY * i);
+                  sb.append("        ");
+                  sb.append(new String(buffer.array(), 0, offset, length));
+                  sb.append("\n");
+               }
+            }
+            break;
+         case TX_PREPARED:
+            sb.append("TX_PREPARED\n");
+            sb.append("    Local transaction id: ");
+            sb.append(buffer.getLong());
+            sb.append("\n");
+            countOfDirEntries = buffer.getShort();
+            sb.append("    Inbound format id: ");
+            sb.append(buffer.getInt());
+            sb.append("\n");
+            gidLen = buffer.getShort();
+            sb.append("    Global transaction id: ");
+            gid = new byte[gidLen];
+            buffer.get(gid);
+            sb.append(new String(gid, 0));
+            sb.append("\n");
+            sb.append("    Recovery coordinator: ");
+            // Get varField 0
+            length = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_SHORT);
+            offset = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY); 
+            sb.append(new String(buffer.array(), 0, offset, length));
+            sb.append("\n");
+            if (countOfDirEntries > 1)
+            {
+               sb.append("    Resources:\n");
+               // Get varField 1, ..., varField N (where N = countOfDirEntries - 1)
+               for (int i = 1; i < countOfDirEntries; i++)
+               {
+                  length = buffer.getShort(recLen - CHKSUM_LEN  
+                                                  - SIZEOF_SHORT 
+                                                  - SIZEOF_DIR_ENTRY * i);
+                  offset = buffer.getShort(recLen - CHKSUM_LEN 
+                                                  - SIZEOF_DIR_ENTRY 
+                                                  - SIZEOF_DIR_ENTRY * i);
+                  sb.append("        ");
+                  sb.append(new String(buffer.array(), 0, offset, length));
+               }
+            }
+            break;
+         case JCA_TX_PREPARED:
+            sb.append("JCA_TX_PREPARED\n");
+            sb.append("    Local transaction id: ");
+            sb.append(buffer.getLong());
+            sb.append("\n");
+            countOfDirEntries = buffer.getShort();
+            sb.append("    Inbound format id: ");
+            sb.append(buffer.getInt());
+            sb.append("\n");
+            gidLen = buffer.getShort();
+            sb.append("    Global transaction id: ");
+            gid = new byte[gidLen];
+            buffer.get(gid);
+            sb.append(new String(gid, 0));
+            sb.append("\n");
+            sb.append("    Inbound branch qualifier: ");
+            // Get varField 0
+            length = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_SHORT);
+            offset = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY); 
+            sb.append(new String(buffer.array(), 0, offset, length));
+            sb.append("\n");
+            if (countOfDirEntries > 1)
+            {
+               sb.append("    Resources:\n");
+               // Get varField 1, ..., varField N (where N = countOfDirEntries - 1)
+               for (int i = 1; i < countOfDirEntries; i++)
+               {
+                  length = buffer.getShort(recLen - CHKSUM_LEN  
+                                                  - SIZEOF_SHORT 
+                                                  - SIZEOF_DIR_ENTRY * i);
+                  offset = buffer.getShort(recLen - CHKSUM_LEN 
+                                                  - SIZEOF_DIR_ENTRY 
+                                                  - SIZEOF_DIR_ENTRY * i);
+                  sb.append("        ");
+                  sb.append(new String(buffer.array(), 0, offset, length));
+               }
+            }
+            break;
+         case TX_COMMITTED:
+            sb.append("TX_COMMITTED\n");
+            sb.append("    Local transaction id: ");
+            sb.append(buffer.getLong());
+            sb.append("\n");
+            break;
+         case TX_END:
+            sb.append("TX_END\n");
+            sb.append("    Local transaction id: ");
+            sb.append(buffer.getLong());
+            sb.append("\n");
+            break;
+         case HEUR_STATUS:
+            sb.append("HEUR_STATUS\n");
+            sb.append("    Local transaction id: ");
+            sb.append(buffer.getLong());
+            sb.append("\n");
+            countOfDirEntries = buffer.getShort();
+            sb.append("    Transaction status: ");
+            byte status = buffer.get();
+            sb.append(TxUtils.getStatusAsString(status));
+            sb.append("\n");
+            sb.append("    Heuristic status: ");
+            byte heurCode = buffer.get();
+            if (heurCode != 0)
+               sb.append(TxUtils.getXAErrorCodeAsString(heurCode));
+            else
+               sb.append("NONE");
+            sb.append("\n");
+            sb.append("    Locally-detected heuristic hazard: ");
+            sb.append((buffer.get() != 0) ? "yes" : "no");
+            sb.append("\n");
+            sb.append("    Foreign transaction: ");
+            boolean foreignTransaction = (buffer.get() != 0);
+            sb.append((foreignTransaction) ? "yes" : "no");
+            sb.append("\n");
+            sb.append((foreignTransaction) ? "    Inbound format id: " 
+                                           : "    Format id: ");
+            sb.append(buffer.getInt());
+            sb.append("\n");
+            gidLen = buffer.getShort();
+            sb.append("    Global transaction id: ");
+            gid = new byte[gidLen];
+            buffer.get(gid);
+            sb.append(new String(gid, 0));
+            sb.append("\n");
+            // Get varField 0
+            length = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_SHORT);
+            if (length > 0)
+            {
+               sb.append("    Inbound branch qualifier: ");
+               offset = buffer.getShort(recLen - CHKSUM_LEN - SIZEOF_DIR_ENTRY);
+               sb.append(new String(buffer.array(), 0, offset, length));
+               sb.append("\n");
+            }
+            // Get varField 1
+            length = buffer.getShort(recLen - CHKSUM_LEN  
+                                            - SIZEOF_SHORT 
+                                            - SIZEOF_DIR_ENTRY);
+            if (length > 0)
+            {
+               offset = buffer.getShort(recLen - CHKSUM_LEN 
+                                               - SIZEOF_DIR_ENTRY 
+                                               - SIZEOF_DIR_ENTRY);
+               sb.append("    XAResource heuristics:\n");
+               for (int i = 0; i < length; i++)
+               {
+                  sb.append("        ");
+                  heurCode = buffer.get(offset + i);
+                  sb.append(TxUtils.getXAErrorCodeAsString(heurCode));
+                  sb.append("\n");
+               }
+            }
+            // Get varField 2, ..., varField N (where N = countOfDirEntries - 1)
+            if (countOfDirEntries > 2)
+            {
+               sb.append("    Remote resource heuristics:\n");
+               for (int i = 2; i < countOfDirEntries; i++)
+               {
+                  length = buffer.getShort(recLen - CHKSUM_LEN  
+                                                  - SIZEOF_SHORT 
+                                                  - SIZEOF_DIR_ENTRY * i);
+                  offset = buffer.getShort(recLen - CHKSUM_LEN 
+                                                  - SIZEOF_DIR_ENTRY 
+                                                  - SIZEOF_DIR_ENTRY * i);
+                  heurCode = buffer.get(offset); 
+                  sb.append("        ");
+                  sb.append(TxUtils.getXAErrorCodeAsString(heurCode));
+                  sb.append(" - ");
+                  sb.append(new String(buffer.array(), 0, 
+                                       offset + 1, length - 1));
+                  sb.append("\n");
+               }
+            }
+            break;
+         case HEUR_FORGOTTEN:
+            sb.append("HEUR_FORGOTTEN\n");
+            sb.append("Local transaction id: ");
+            sb.append(buffer.getLong(FULL_HEADER_LEN + SIZEOF_BYTE));
+            sb.append("\n");
+            break;
+         default:
+            sb.append("INVALID\n");
+            break;
+      }
+      buffer.position(0);
+      return sb.toString();
+   }
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/LogRestarter.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/LogRestarter.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/LogRestarter.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,119 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jboss.logging.Logger;
+
+/**
+ * This class supports asynchronous restarting of log files. Restarting a log
+ * file means overwriting its entire content with null bytes and returning the
+ * file to the pool of clean log files maintained by a <code>BatchWriter</code>. 
+ * A <code>LogRestarter</code> encapsulates a queue of log files to be 
+ * restarted and implements a background thread that restarts the log files 
+ * in the queue. Its sole purpose is to avoid the delay of cleaning up a log 
+ * file.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+class LogRestarter 
+      implements Runnable
+{
+   /**
+    * Class <code>Logger</code>, for trace messages.
+    */
+   private static Logger errorLog = Logger.getLogger(LogRestarter.class);
+
+   /** This flag can be set to false to stop the log restarter thread. */  
+   private boolean running = true;
+   
+   /** Queue of <code>BatchLog</code> instances to be restarted. */
+   private List logsToRestart = new ArrayList();
+
+   /**
+    * Takes a log file to be asynchronously restarted. 
+    * 
+    * @param log a <code>BatchLog</code> instance to be restarted.
+    */
+   synchronized void add(BatchLog log)
+   {
+      logsToRestart.add(log);
+      notify();
+   }
+
+   /**
+    * Stops the log restarter thread.
+    */
+   synchronized void stop()
+   {
+      running = false;
+      notify();
+   }
+
+   /**
+    * The log restarter thread body.
+    */
+   public void run()
+   {
+      BatchLog log;
+      
+      while (running)
+      {
+         synchronized (this)
+         {
+            if (logsToRestart.size() > 0)
+            {
+               log = (BatchLog) logsToRestart.remove(0);
+            }
+            else
+            {
+               try
+               {
+                  wait();
+               }
+               catch (InterruptedException e)
+               {
+                  if (!running) 
+                     break;
+               }
+               continue;
+            }
+         }
+
+         try
+         {
+            log.restart();
+         }
+         catch (IOException e)
+         {
+            errorLog.error("Error cleaning up transaction log " 
+                           + log.getFilename(), e);
+         }
+      }
+
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/PendingWriteRequest.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/PendingWriteRequest.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/PendingWriteRequest.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,216 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import EDU.oswego.cs.dl.util.concurrent.Latch;
+
+import java.nio.ByteBuffer;
+
+/**
+ * Represents one pending "write record" operation to be performed on 
+ * a <code>BatchLog</code>.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+class PendingWriteRequest
+{
+   /** 
+    * Type of a tx committed or tx prepared record for a transaction that 
+    * involves multiple TMs. 
+    */ 
+   static final int TYPE_TX_MULTI_TM = 1;
+   
+   /**
+    * Type of a tx committed record for a transaction that involves a single TM. 
+    */ 
+   static final int TYPE_TX_SINGLE_TM = 0;
+   
+   /** 
+    * Type of an tx end record for a transaction that involves a multiple TMs. 
+    * There is no tx end record for a transaction that involves a single TM.  
+    * */ 
+   static final int TYPE_END = -1;
+   
+   /**
+    * A buffer containing the record to be written to a <code>BatchLog</code>.
+    */
+   private ByteBuffer buffer;
+   
+   /**
+    * The <code>BatchLog</code> to which the record should be written.
+    */
+   private BatchLog log;
+
+   /**
+    * A <code>Latch</code> that will be released after the pending write 
+    * operation is performed. Whoever actually performs the write operation 
+    * is responsible for releasing this latch.
+    */
+   private Latch latch;
+   
+   /**
+    * The <code>TxCompletionHandler</code> associated with a commit record or 
+    * with a prepare record to be written to a log. The two-phase commit
+    * implementation should invoke the completion handler at the end of the 
+    * second phase of the protocol for the transaction that has the pending
+    * commit or prepare record.  
+    */
+   private TxCompletionHandler completionHandler;
+   
+   /**
+    * Either contains an exception thrown during an attempt to perform the 
+    * pending write record operation, or null if no exception was thrown.
+    */
+   private Exception failure;
+   
+   /**
+    * Indicates the type of the record to be written to a <code>BatchLog</code>.
+    * Possible values are <code>TYPE_TX_MULTI_TM</code>, 
+    * <code>TYPE_TX_SINGLE_TM</code>, and <code>TX_END</code>.
+    * 
+    */
+   private int type;
+
+   /**
+    * Creates a <code>PendingWriteRequest</code> for a commit or prepare
+    * record.
+    * 
+    * @param buffer a buffer containing the commit or prepare record
+    * @param latch  a latch that will be released after the pending write 
+    *               operation is performed  
+    * @param multiTmTransaction true if the record refers to a transaction
+    *               that involves multiple TMs (and requires a tx end record),
+    *               false if the record refers to a transaction that involves
+    *               a single TM (and requires no tx end record).
+    */
+   PendingWriteRequest(ByteBuffer buffer, 
+                       Latch latch, 
+                       boolean multiTmTransaction)
+   {
+      this.buffer = buffer;
+      this.latch = latch;
+      this.log = null;
+      type = (multiTmTransaction) ? TYPE_TX_MULTI_TM : TYPE_TX_SINGLE_TM;
+   }
+
+   /**
+    * Creates a <code>PendingWriteRequest</code> for a tx end record to be 
+    * written to a given <code>BatchLog</code>. This method takes an argument 
+    * that speficies the <code>BatchLog</code> because the tx end record for a 
+    * transaction must be written to the same <code>BatchLog</code> as the 
+    * transaction's tx committed or tx prepared record.
+    * 
+    * @param buffer a buffer containing the tx end record
+    * @param latch  a latch that will be released after the pending write 
+    *               operation is performed  
+    * @param log    the <code>BatchLog</code> to which the tx end record should
+    *               be written.
+    */
+   PendingWriteRequest(ByteBuffer buffer, Latch latch, BatchLog log)
+   {
+      this.buffer = buffer;
+      this.latch = latch;
+      this.log = log;
+      type = TYPE_END;
+   }
+
+   /**
+    * Waits until this pending write record is performed. If an exception
+    * was thrown while attempting to perform the write record, this method
+    * throws a <code>RuntimeException</code> that contains the original
+    * exception.
+    * 
+    * @return the completion handler associated with a tx committed or with
+    *         a tx prepared record.
+    */
+   TxCompletionHandler waitTilDone()
+   {
+      try
+      {
+         latch.acquire();
+         if (failure != null) 
+            throw failure;
+         return completionHandler;
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException(e);
+      }
+   }
+
+   /**
+    * Gets the buffer containing the record to be written.
+    * 
+    * @return a <code>ByteBuffer</code> instance containing the record.
+    */
+   ByteBuffer getBuffer()
+   {
+      return buffer;
+   }
+   
+   /**
+    * Gets the type of the record to be written.
+    * 
+    * @return <code>TYPE_TX_MULTI_TM</code>, or
+    *         <code>TYPE_TX_SINGLE_TM</code>, or <code>TX_END</code>.
+    */
+   int getType()
+   {
+      return type;
+   }
+
+   /**
+    * Gets the <code>BatchLog</code> to which the record should be written.
+    * 
+    * @return the <code>BatchLog</code> to which the record should be written.
+    */
+   BatchLog getLogger()
+   {
+      return log;
+   }
+
+   /**
+    * Associates a completion handler with the pending tx committed or 
+    * tx prepared record.
+    * 
+    * @param completionHandler the new completion handler.
+    */
+   void setCompletionHandler(TxCompletionHandler completionHandler)
+   {
+      this.completionHandler = completionHandler;
+   }
+
+   /**
+    * Stores an exception thrown during an attempt to perform the pending 
+    * write operation. The method <code>waitTilDone</code> will throw a 
+    * a <code>RuntimeException</code> that contains the stored exception.
+    * 
+    * @param failure an exception to be stored.
+    */
+   void setFailure(Exception failure)
+   {
+      this.failure = failure;
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/Recoverable.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/Recoverable.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/Recoverable.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,43 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/**
+ * Interface that gives access to a XA resource at recovery time.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public interface Recoverable
+{
+   public String getId();
+
+   public XAResource getResource();
+
+   public Xid[] scan() throws XAException;
+
+   public void cleanupResource();
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogReader.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogReader.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogReader.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,81 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import java.util.List;
+
+/**
+ * Interface that gives access to the information in a recovery log file.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface RecoveryLogReader
+{
+   /**
+    * Gets the name of the underlying log file.
+    * 
+    * @return the name of the log file.
+    */
+   String getLogFileName();
+
+   /**
+    * Gets the branch qualifier string read from the log file.
+    * 
+    * @return the branch qualifier read from the log file.
+    */
+   String getBranchQualifier();
+
+   /**
+    * Recovers transaction information from the log file. 
+    * 
+    * @param committedSingleTmTransactions a <code>List</code> of 
+    *           <code>LogRecord.Data</code> instances with one element per
+    *           committed single-TM transaction logged to the log file
+    * @param committedMultiTmTransactions a <code>List</code> of 
+    *           <code>LogRecord.Data</code> instances with one element per
+    *           committed  multi-TM transaction that has not yet completed the
+    *           second phase of the 2PC protocol when the server crashed 
+    * @param inDoubtTransactions a <code>List</code> of 
+    *           <code>LogRecord.Data</code> instances with one element per
+    *           foreign transaction that arrived at the server via DTM/OTS
+    *           context propagation and was in the in-doubt state (i.e.,
+    *           replied to prepare with a commit vote but has not yet received
+    *           information on the transaction outcome) when the server crashed
+    * @param inDoubtJcaTransactions a <code>List</code> of 
+    *           <code>LogRecord.Data</code> instances with one element per
+    *           foreign transaction that arrived at the server via JCA 
+    *           transaction inflow and was in the in-doubt state (i.e., replied
+    *           to prepare with a commit vote and was waiting for information 
+    *           on the transaction outcome) when the server crashed.
+    */
+   void recover(List committedSingleTmTransactions,
+                List committedMultiTmTransactions,
+                List inDoubtTransactions,
+                List inDoubtJcaTransactions);
+
+   /**
+    * Removes the underlying log file.
+    */
+   void finishRecovery();
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogger.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogger.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLogger.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,155 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import javax.transaction.xa.Xid;
+
+/**
+ * This is the main interface of the recovery logger service.
+ * 
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface RecoveryLogger
+{
+   /**
+    * Should be invoked at the end of the voting phase of the two-phase commit
+    * protocol for a transaction started at this virtual machine.
+    * 
+    * @param localTransactionId  the local id of the transaction
+    * @param resources an array of stringfied references to the remote
+    *                  <code>Resource</code> instances that voted commit. 
+    * @return a handler that should be invoked at the end of the second phase
+    *         of the two-phase commit protocol for the transaction (if the
+    *         transaction is not heuristically completed) or after any 
+    *         heuristic decisions are cleared.
+    */
+   public TxCompletionHandler saveCommitDecision(long localTransactionId,
+                                                 String[] resources);
+
+   /**
+    * Should be invoked at the end of the voting phase of the two-phase commit
+    * protocol for a foreign transaction that arrived at this virtual machine
+    * in a transaction context propagated along with a remote invocation.
+    * 
+    * @param localTransactionId  the local id assigned to the transaction
+    * @param inboundFormatId     the format id field of the incoming 
+    *                            transaction context
+    * @param globalTransactionId the global transaction id field of the
+    *                            incoming transaction context
+    * @param recoveryCoordinator a stringfied reference to the remote
+    *                            <code>RecoveryCoordinator</code> instance
+    * @param resources an array of stringfied references to the remote
+    *                  <code>Resource</code> instances that voted commit. 
+    * @return a handler that should be invoked at the end of the second phase
+    *         of the two-phase commit protocol for the transaction (if the
+    *         transaction is not heuristically completed) or after any 
+    *         heuristic decisions are cleared.
+    */
+   public TxCompletionHandler savePrepareDecision(long localTransactionId,
+                                                  int inboundFormatId,
+                                                  byte[] globalTransactionId,
+                                                  String recoveryCoordinator,
+                                                  String[] resources);
+
+   /**
+    * Should be invoked at the end of the voting phase of the two-phase commit
+    * protocol for a foreign transaction that arrived at this virtual machine
+    * via JCA transaction inflow.
+    * 
+    * @param localTransactionId the local id assigned to the transaction
+    * @param inboundXid         the inbound <code>Xid</code> 
+    * @param resources an array of stringfied references to the remote
+    *                  <code>Resource</code> instances that voted commit. 
+    * @return a handler that should be invoked at the end of the second phase
+    *         of the two-phase commit protocol for the transaction (if the
+    *         transaction is not heuristically completed) or after any 
+    *         heuristic decisions are cleared.
+    */
+   public TxCompletionHandler savePrepareDecision(long localTransactionId,
+                                                  Xid inboundXid,
+                                                  String[] resources);
+
+   /**
+    * Should be invoked for a heuristically completed transaction, at the end
+    * of the second phase of the two-phase commit protocol.
+    * 
+    * @param localTransactionId the local id assigned to the transaction
+    * @param foreignTx true for a foreign transaction, false otherwise 
+    * @param formatId the format id field of the transaction's <code>Xid</code>
+    * @param globalTransactionId the global id field of the transaction's 
+    *            <code>Xid</code>
+    * @param inboundBranchQualifier the inbound branch qualifier, in the case 
+    *            of a foreign transaction that has been imported via JCA 
+    *            transaction inflow, or null otherwise   
+    * @param transactionStatus the transaction status (either 
+    *             <code>javax.transaction.Status.STATUS_COMMITTED</code> or
+    *             <code>javax.transaction.Status.STATUS_ROLLEDBACK</code>)
+    * @param heurStatusCode the heuristic status code, which takes the same
+    *             values as the <code>errorCode</code> field of 
+    *             <code>javax.transaction.xa.XAException</code>
+    * @param locallyDetectedHeuristicHazard true if a heuristic hazard was 
+    *             detected locally and is still outstanding
+    * @param xaResourceHeuristics array of heuristic status codes for the 
+    *             <code>XAResource</code> that are in heuristic states
+    * @param remoteResourceHeuristics array of <code>HeuristicStatus</code>
+    *             instances for the remote resources that are in heuristic
+    *             states. 
+    */
+   public void saveHeuristicStatus(long localTransactionId,
+                                   boolean foreignTx,
+                                   int formatId, 
+                                   byte[] globalTransactionId,
+                                   byte[] inboundBranchQualifier,
+                                   int transactionStatus,
+                                   int heurStatusCode,
+                                   boolean locallyDetectedHeuristicHazard,
+                                   int[] xaResourceHeuristics, 
+                                   HeuristicStatus[] remoteResourceHeuristics);
+   
+   /**
+    * Clears the heuristic status of a heuristically completed transaction.
+    * 
+    * @param localTransactionId the local id assigned to the transaction
+    */
+   public void clearHeuristicStatus(long localTransactionId);
+   
+   /**
+    * Should be invoked at recovery time to obtain an array of reader objects
+    * that access the existing transaction log files.
+    * 
+    * @return an array that contains one <code>RecoveryLogReader</code> 
+    *         instance per transaction log file.
+    */
+   public RecoveryLogReader[] getRecoveryLogs();
+
+   /**
+    * Should be invoked at recovery time to obtain an array of reader objects
+    * that access the existing heuristic status log files.
+    * 
+    * @return an array that contains one <code>HeuristicStatusLogReader</code> 
+    *         instance per heuristic status log file.
+    */
+   public HeuristicStatusLogReader[] getHeuristicStatusLogs();
+   
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLoggerInstance.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLoggerInstance.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryLoggerInstance.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,33 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+/**
+ * Interface that gives access to a <code>RecoveryLogger</code> instance.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public interface RecoveryLoggerInstance
+{
+   RecoveryLogger getInstance();
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManager.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManager.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManager.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,1053 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.transaction.Status;
+import javax.transaction.xa.XAException;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+import org.jboss.logging.Logger;
+import org.jboss.tm.TxManager;
+import org.jboss.tm.TxUtils;
+import org.jboss.tm.XidFactoryBase;
+
+/**
+ * A <code>RecoveryManager</code> object manages the crash recovery process. 
+ * At recovery time, the <code>RecoveryManagerService</code> creates a 
+ * <code>RecoveryManager</code> instance that interacts with the 
+ * <code>RecoveryLogger</code> to read the transaction logs and identify
+ * the transactions that were active at the time of the crash. The 
+ * <code>RecoveryManager</code> knows how to perform crash recovery for 
+ * transactions involving <code>XAResource</code>s that correspond to
+ * <code>Recoverable</code> objects known in advance.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class RecoveryManager
+{
+   /**
+    * Class <code>Logger</code> for trace messages.
+    */
+   private static Logger log = Logger.getLogger(RecoveryManager.class.getName());
+   
+   /**
+    * A <code>XAResourceAccess</code> implementation that calls 
+    * <code>cleanUpResource</code> on a <code>Recoverable</code> instance 
+    * if the <code>Recoverable</code> will not be used anymore. The
+    * last <code>release</code> call on an <code>XAResourceAccess</code>
+    * instance closes the underlying <code>XAConnection</code>. 
+    */
+   public static class XAResourceAccessImpl
+      implements XAResourceAccess
+   {
+      private Recoverable recoverable;
+      private int refCount;
+      
+      // not public -- called only by the XAResXids constructor (see below)
+      XAResourceAccessImpl(Recoverable recoverable)
+      {
+         this.recoverable = recoverable;
+         refCount = 1;
+      }
+      
+      // not public -- called only by the XAWork constructor
+      synchronized XAResourceAccess duplicate()
+      {
+         refCount++;
+         return this;
+      }
+      
+      public synchronized void release()
+      {
+         if (refCount <= 0)
+            log.warn("release called, but refCount=" + refCount +  
+                     ", this=" + this, new Throwable("[Stack trace]"));
+         
+         if (--refCount == 0)
+            recoverable.cleanupResource();
+      }
+      
+   }
+
+   /**
+    * "Struct class" that groups together a <code>Recoverable</code> object,  
+    * the <code>XAResource</code> represented by the <code>Recoverable</code>,    
+    * and a set of <code>Xids</code> that still need to be processed. 
+    */
+   private static class XAResourceXids
+   {
+      public Recoverable recoverable;
+      public XAResource resource;
+      public XAResourceAccessImpl resourceAccess;
+      public Set xids;
+      
+      XAResourceXids(Recoverable recoverable)
+      {
+         this.recoverable = recoverable;
+         this.resource = recoverable.getResource();
+         // Note that the XAResourceAccess constructor is *not* called if a 
+         // throwable occurs in recoverable.getResource().
+         this.resourceAccess = new XAResourceAccessImpl(recoverable);
+         this.xids = new HashSet();
+      }
+   }
+   
+   /**
+    * This implementation of <code>TxCompletionHandler</code> handles  
+    * transaction completion for transactions recreated by the recovery
+    * process.
+    */
+   private static class CompletionHandler
+      implements TxCompletionHandler
+   {
+      /** 
+       * The log reader associated with the transaction whose completion
+       * is handled by this <code>CompletionHandler</code>.
+       */
+      private RecoveryLogReader reader;
+      
+      /** 
+       * Number of pending transactions associated with the log reader. 
+       */
+      private int pendingTransactions;
+
+      /**
+       * Creates a new <code>CompletionHandler</code>.
+       * 
+       * @param reader log reader associated with the transaction whose 
+       *               completion will be handled by the new 
+       *               <code>CompletionHandler</code> 
+       * @param pendingTransactions number of pending transactions 
+       *               associated with the log reader above.  
+       */
+      CompletionHandler(RecoveryLogReader reader, int pendingTransactions)
+      {
+         this.reader = reader;
+         this.pendingTransactions = pendingTransactions;
+      }
+
+      /**
+       * Signals the end of the two-phase commit protocol for a committed 
+       * transaction. This method should be invoked when the second phase of the 
+       * two-phase commit protocol completes successfully.
+       *
+       * @param localTransactionId the local id of the completed transaction.
+       */
+      public void handleTxCompletion(long localTransactionId)
+      {
+         if (--pendingTransactions == 0)
+            reader.finishRecovery();
+      }
+   }
+   
+   /**
+    * The Xid factory used by this <code>RecoveryManager</code>.
+    */
+   private XidFactoryBase xidFactory;
+
+   /**
+    * The transaction manager used by this <code>RecoveryManager</code>.
+    */
+   private TxManager txManager;
+
+   /**
+    * The recovery logger used by this <code>RecoveryManager</code>.
+    */
+   private RecoveryLogger recoveryLogger;
+
+   /**
+    * Constructs a new <code>RecoveryManager</code>.
+    *
+    * @param xidFactory the Xid factory that will be used by the new 
+    *                   <code>RecoveryManager</code>
+    * @param txManager  the transaction manager that will be used by the new 
+    *                   <code>RecoveryManager</code>
+    * @param recoveryLogger the recovery logger that will be used by the new 
+    *                   <code>RecoveryManager</code>
+    */
+   public RecoveryManager(XidFactoryBase xidFactory, 
+                          TxManager txManager, 
+                          RecoveryLogger recoveryLogger)
+   {
+      this.xidFactory = xidFactory;
+      this.txManager = txManager;
+      this.recoveryLogger = recoveryLogger;
+   }
+   
+   /**
+    * Performs crash recovery. This method reads the transaction logs, 
+    * identifies the transactions that were active at the time of the crash,
+    * and performs crash recovery actions for each such transaction, which
+    * may involve any subset of the <code>XAResource</code>s associated with 
+    * a given list of <code>Recoverable</code> objects 
+    * 
+    * @param rcoverables a list of <code>Recoverable</code> objects whose
+    *                    <code>XAResource</code>s may be involved in active
+    *                    transactions.
+    */
+   public void recover(ArrayList recoverables)
+   {
+      Map heuristicallyCompletedTransactions = new HashMap();
+
+      HeuristicStatusLogReader[] heurStatusLogReaders = 
+         recoveryLogger.getHeuristicStatusLogs();
+            
+      if (heurStatusLogReaders != null)
+      {
+         // Get the heuristically completed transactions 
+         // from the existing heuristic status log files
+         for (int i = 0; i < heurStatusLogReaders.length; i++)
+            heurStatusLogReaders[i].recover(heuristicallyCompletedTransactions);
+
+         // Save the heuristic status of those transactions 
+         // to the current heuristic status log file
+         Iterator heurIt = 
+            heuristicallyCompletedTransactions.keySet().iterator();
+         while (heurIt.hasNext())
+         {
+            Long localId = (Long) heurIt.next();
+            LogRecord.HeurData heurData = 
+               (LogRecord.HeurData) heuristicallyCompletedTransactions.get(
+                                                                      localId);
+            recoveryLogger.saveHeuristicStatus(
+                                      heurData.localTransactionId,
+                                      heurData.foreignTx,
+                                      heurData.formatId, 
+                                      heurData.globalTransactionId,
+                                      heurData.inboundBranchQualifier,
+                                      heurData.transactionStatus,
+                                      heurData.heuristicStatusCode,
+                                      heurData.locallyDetectedHeuristicHazard,
+                                      heurData.xaResourceHeuristics, 
+                                      heurData.remoteResourceHeuristics);
+         }
+
+         // Get rid of the existing heuristic status log files 
+         for (int i = 0; i < heurStatusLogReaders.length; i++)
+            heurStatusLogReaders[i].finishRecovery();
+      }
+      
+      RecoveryLogReader[] readers = recoveryLogger.getRecoveryLogs();
+      if (readers == null || readers.length == 0) 
+         return;
+
+      // Obtain the set of branch qualifiers generated by this TM.
+      Set readerBranchQualifiers = new HashSet();
+      for (int i = 0; i < readers.length; i++)
+      {
+         String branchQualifier = null;
+         try
+         {
+            branchQualifier = readers[i].getBranchQualifier();
+         }
+         catch (Exception e)
+         {
+            log.error("logfile corrupted: " 
+                      + readers[i].getLogFileName(), e);
+         }
+         readerBranchQualifiers.add(branchQualifier);
+         log.info("will recover transactions with branch qualifier " + 
+                  branchQualifier + 
+                  " (logFile: " + readers[i].getLogFileName() + ")");
+      }
+
+      Map toRecoverMap = new HashMap();
+      try
+      {
+         // Populate a map whose keys are Recoverable ids and whose values are 
+         // XAResourceXids objects, each of which contains the set of Xids of 
+         // the active XA transaction branches that involve a given XAResource 
+         // and that require recovery actions.
+         for (int i = 0; i < recoverables.size(); i++)
+         {
+            Recoverable rec = (Recoverable) recoverables.get(i);
+            XAResourceXids xaResXids;
+            try
+            {
+               xaResXids = new XAResourceXids(rec);
+            }
+            catch (Throwable t)
+            {
+               throw new RuntimeException("Unable to getResource: " 
+                                          + rec.getId() 
+                                          + " aborting recovery.", t);
+            }
+            toRecoverMap.put(rec.getId(), xaResXids);
+            try
+            {
+               xaResXids.xids.addAll(pruneXidList(rec.scan(), 
+                                                  readerBranchQualifiers,
+                                                  rec.getId()));
+            }
+            catch (XAException e)
+            {
+               // TODO also, what to do if this fails? 
+               // Should we go on and still try to recover other resources?
+               throw new RuntimeException("Unable to scan: " + rec.getId(), e);
+            }
+         }
+         
+         // Perform the recovery actions.
+         recover(readers, heuristicallyCompletedTransactions, toRecoverMap);
+      }
+      finally
+      {
+         cleanupRecoverables(toRecoverMap.values().iterator());
+      }
+   }
+
+   /**
+    * Performs crash recovery using a given array of log readers and a map 
+    * with information on the active XA transaction branches for which 
+    * recovery actions must be taken.
+    *
+    * @param readers      an array of transaction log readers
+    * @param heuristicallyCompletedTransactions
+    * @param toRecoverMap a map whose keys are <code>Recoverable</code> ids 
+    *                     and whose values are <code>XAResourceXids<code> 
+    *                     objects, each of which contains the set of 
+    *                     <code>Xids</code> for the active XA transaction 
+    *                     branches that involve a given <code>XAResource<code>
+    *                     and that require recovery actions.
+    */
+   private void recover(RecoveryLogReader[] readers,
+                        Map heuristicallyCompletedTransactions,
+                        Map toRecoverMap)
+   {
+      boolean presumeRollback = true;
+      CorruptedLogRecordException corruptedLogRecordException = null;
+      
+      // Recreate the pending committed and in-doubt transactions, including the
+      // ones that are pending just because they were heuristically completed.
+      for (int i = 0; i < readers.length; i++)
+      {
+         log.info("recovering log file " + readers[i].getLogFileName());
+         List committedSingleTmTransactions = new ArrayList();
+         List committedMultiTmTransactions = new ArrayList();
+         List inDoubtTransactions = new ArrayList();
+         List inDoubtJcaTransactions = new ArrayList();
+
+         try
+         {
+            readers[i].recover(committedSingleTmTransactions,
+                               committedMultiTmTransactions,
+                               inDoubtTransactions,
+                               inDoubtJcaTransactions);
+         }
+         catch (CorruptedLogRecordException e)
+         {
+            log.trace("reader threw CorruptedLogRecordException with " +
+                      "disablePresumedRollback=" + e.disablePresumedRollback);
+            corruptedLogRecordException = e;
+            if (corruptedLogRecordException.disablePresumedRollback)
+               presumeRollback = false;
+         }
+         
+         int pendingTransactions =  committedSingleTmTransactions.size() +
+                                    committedMultiTmTransactions.size() +
+                                    inDoubtTransactions.size() +
+                                    inDoubtJcaTransactions.size();
+         
+         if (pendingTransactions == 0)
+            readers[i].finishRecovery();
+         else
+         {
+            CompletionHandler completionHandler = 
+               new CompletionHandler(readers[i], pendingTransactions); 
+        
+            resumePendingTransactions(heuristicallyCompletedTransactions,
+                                      committedSingleTmTransactions, 
+                                      committedMultiTmTransactions,
+                                      inDoubtTransactions,
+                                      inDoubtJcaTransactions,
+                                      toRecoverMap,
+                                      completionHandler);
+         }
+      }
+      
+      // Recreate the remaining heuristically completed transactions 
+      // (these transactions are in the rolledback state).
+      Iterator heurIt = heuristicallyCompletedTransactions.keySet().iterator();
+      while (heurIt.hasNext())
+      {
+         Long localId = (Long) heurIt.next();
+         LogRecord.HeurData heurData = 
+            (LogRecord.HeurData) heuristicallyCompletedTransactions.get(localId);
+         heurIt.remove(); // heuristicallyCompletedTransactions.remove(localId) 
+         byte[] globalId = heurData.globalTransactionId;
+         List xaResourcesWithHeuristics = getXAWork(globalId, toRecoverMap); 
+         txManager.recreateTransaction(heurData, 
+                                       xaResourcesWithHeuristics, 
+                                       null /* no completion handler */);
+      }
+      
+      if (!presumeRollback)
+      {
+         log.info("PRESUMED ROLLBACK IS DISABLED DUE TO LOG FILE CORRUPTION.");
+      }
+      
+      // Rollback the transactions that remained in the toRecoverMap.
+      // (This is presumed rollback.)
+      Iterator rit = toRecoverMap.values().iterator();
+      while (rit.hasNext())
+      {
+         XAResourceXids xaResXids = (XAResourceXids) rit.next();
+         Iterator it = xaResXids.xids.iterator();
+         while (it.hasNext())
+         {
+            Xid xid = (Xid) it.next();
+            if (!presumeRollback)
+            {
+               log.info("WOULD ROLLBACK " + xidFactory.toString(xid) + 
+                        " ON RECOVERABLE XAResource " + 
+                        xaResXids.recoverable.getId() + 
+                        ", BUT PRESUMED ROLLBACK IS DISABLED");
+            }
+            else
+            {
+               try
+               {
+                  xaResXids.resource.rollback(xid);
+                  log.info("rolledback " + xidFactory.toString(xid) +  
+                           " on recoverable XAResource " + 
+                           xaResXids.recoverable.getId());
+               }
+               catch (XAException e)
+               {
+                  log.warn("XAException in recover (when rolling back " +
+                           "res " + xaResXids.recoverable.getId() + ", xid=" + 
+                           xidFactory.toString(xid) + "): errorCode="
+                           + TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+                  // TODO: check the errorCode and retry the rollback if
+                  //       XAER_RMFAIL or XAER_RMFAIL.
+               }
+            }
+         }
+      }
+      
+      if (corruptedLogRecordException != null)
+         throw corruptedLogRecordException;
+   }
+
+   /**
+    * Resumes the pending transactions specified by the <code>List</code>
+    * arguments.
+    * 
+    * @param committedSingleTmTransactions a list of 
+    *                                      <code>LogRecord.Data</code> objects
+    *                                      for the committed single-TM 
+    *                                      transactions
+    * @param committedMultiTmTransactions a list of <code>LogRecord.Data</code>
+    *                                     objects for the committed multi-TM 
+    *                                     transactions
+    * @param inDoubtTransactions a list of <code>LogRecord.Data</code> objects
+    *                            for the in-doubt transactions that entered
+    *                            this virtual machine in transaction contexts 
+    *                            propagated along with remote method invocations
+    * @param inDoubtJcaTransactions a list of <code>LogRecord.Data</code> objects
+    *                               for the in-doubt transactions that entered
+    *                               this virtual machine through JCA transaction 
+    *                               inflow
+    * @param toRecoverMap a map whose keys are <code>Recoverable</code> ids and 
+    *                     whose values are <code>XAResourceXids<code> objects,
+    *                     each of which contains the set of <code>Xids</code>
+    *                     for all active XA transaction branches that involve 
+    *                     a given <code>XAResource<code> and that require 
+    *                     recovery actions           
+    * @param completionHandler a <code>TxCompletionHandler</code> that handles 
+    *                          the completion of all transactions specified by 
+    *                          the preceding arguments.
+    */
+   private void resumePendingTransactions(Map heuristicallyCompletedTransactions,
+                                          List committedSingleTmTransactions,
+                                          List committedMultiTmTransactions,
+                                          List inDoubtTransactions,
+                                          List inDoubtJcaTransactions,
+                                          Map toRecoverMap,
+                                          TxCompletionHandler completionHandler)
+   {    
+      Iterator it;
+      LogRecord.Data data;
+      LogRecord.HeurData heurData;
+      
+      it = committedSingleTmTransactions.iterator();
+      while (it.hasNext())
+      {
+         data = (LogRecord.Data) it.next();
+         byte[] globalId = data.globalTransactionId;
+         Long localId = new Long(data.localTransactionId); 
+         heurData = 
+            (LogRecord.HeurData)heuristicallyCompletedTransactions.get(localId);
+
+         if (heurData != null)
+            heuristicallyCompletedTransactions.remove(localId);
+         
+         if (heurData != null && !heurData.locallyDetectedHeuristicHazard)
+         {
+            List xaResourcesWithHeuristics = getXAWork(globalId, toRecoverMap); 
+            txManager.recreateTransaction(heurData, 
+                                          xaResourcesWithHeuristics,
+                                          completionHandler);
+         }
+         else
+         {
+            // Either heurData is null or it has a 
+            // locally-detected heuristic hazard.
+            
+            List pendingXAWorkList = commitXAWork(globalId, toRecoverMap);
+            if (pendingXAWorkList.isEmpty())
+            {
+               if (heurData == null)
+               {
+                  completionHandler.handleTxCompletion(data.localTransactionId);
+               }
+               else 
+               {
+                  // just the locally-detected heuristic hazard
+                  txManager.recreateTransaction(heurData, 
+                                                pendingXAWorkList,
+                                                completionHandler);
+               }
+            }
+            else
+            {
+               txManager.recreateTransaction(data.localTransactionId, 
+                                             pendingXAWorkList, 
+                                             completionHandler,
+                                             heurData);
+            }
+         }
+      }
+
+      it = committedMultiTmTransactions.iterator();
+      while (it.hasNext())
+      {
+         data = (LogRecord.Data) it.next();
+         byte[] globalId = data.globalTransactionId;
+         Long localId = new Long(data.localTransactionId); 
+         heurData = 
+            (LogRecord.HeurData)heuristicallyCompletedTransactions.get(localId);
+         
+         if (heurData != null)
+            heuristicallyCompletedTransactions.remove(localId);
+         
+         if (heurData != null && !heurData.locallyDetectedHeuristicHazard)
+         {
+            List xaResourcesWithHeuristics = getXAWork(globalId, toRecoverMap); 
+            txManager.recreateTransaction(heurData, 
+                                          xaResourcesWithHeuristics,
+                                          completionHandler);
+         }
+         else
+         {
+            // Either heurData is null or it has a 
+            // locally-detected heuristic hazard.
+            
+            List pendingXAWorkList = commitXAWork(globalId, toRecoverMap);
+            txManager.recreateTransaction(data.localTransactionId,
+                                          pendingXAWorkList,
+                                          data.resources,
+                                          completionHandler,
+                                          heurData);
+         }
+      }
+      
+      it = inDoubtTransactions.iterator();
+      while (it.hasNext())
+      {
+         data = (LogRecord.Data) it.next();
+         byte[] globalId = data.globalTransactionId;
+         Long localId = new Long(data.localTransactionId); 
+         heurData = 
+            (LogRecord.HeurData)heuristicallyCompletedTransactions.get(localId);
+
+         if (heurData != null)
+            heuristicallyCompletedTransactions.remove(localId);
+         
+         if (heurData != null && !heurData.locallyDetectedHeuristicHazard)
+         {
+            heuristicallyCompletedTransactions.remove(localId);
+            List xaResourcesWithHeuristics = getXAWork(globalId, toRecoverMap); 
+            txManager.recreateTransaction(heurData, 
+                                          xaResourcesWithHeuristics,
+                                          completionHandler);
+         }
+         else
+         {
+            // Either heurData is null or it has a 
+            // locally-detected heuristic hazard.
+            
+            if (heurData == null)
+            {
+               List preparedXAWorkList = getXAWork(globalId, toRecoverMap);
+               txManager.recreateTransaction(data.localTransactionId,
+                                             data.inboundFormatId,
+                                             data.globalTransactionId,
+                                             data.recoveryCoordinator,
+                                             preparedXAWorkList,
+                                             data.resources,
+                                             completionHandler,
+                                             null);
+            }
+            else
+            {
+               // locally-detected heuristic hazard
+               if (heurData.transactionStatus == Status.STATUS_COMMITTING)
+               {
+                  List pendingXAWorkList = commitXAWork(globalId, toRecoverMap);
+                  txManager.recreateTransaction(data.localTransactionId,
+                                                data.inboundFormatId,
+                                                data.globalTransactionId,
+                                                data.recoveryCoordinator,
+                                                pendingXAWorkList,
+                                                data.resources,
+                                                completionHandler,
+                                                heurData);
+               }
+               else if (heurData.transactionStatus == Status.STATUS_ROLLING_BACK)
+               {
+                  List pendingXAWorkList = 
+                     rollbackXAWork(globalId, toRecoverMap);
+                  txManager.recreateTransaction(data.localTransactionId,
+                                                data.inboundFormatId,
+                                                data.globalTransactionId,
+                                                data.recoveryCoordinator,
+                                                pendingXAWorkList,
+                                                data.resources,
+                                                completionHandler,
+                                                heurData);
+               }
+               else
+               {
+                  log.warn("Cannot recover tx=" + toString() + 
+                           "\nInconsistent state", 
+                           new Throwable("[Stack trace]"));
+               }
+            }
+         }
+      }
+   
+      it = inDoubtJcaTransactions.iterator();
+      while (it.hasNext())
+      {
+         data = (LogRecord.Data) it.next();
+         byte[] globalId = data.globalTransactionId;
+         Long localId = new Long(data.localTransactionId); 
+         heurData = 
+            (LogRecord.HeurData)heuristicallyCompletedTransactions.get(localId);
+
+         if (heurData != null)
+            heuristicallyCompletedTransactions.remove(localId);
+
+         if (heurData != null && !heurData.locallyDetectedHeuristicHazard)
+         {
+            List xaResourcesWithHeuristics = getXAWork(globalId, toRecoverMap); 
+            txManager.recreateTransaction(heurData, 
+                                          xaResourcesWithHeuristics,
+                                          completionHandler);
+         }
+         else
+         {
+            // Either heurData is null or it has a 
+            // locally-detected heuristic hazard.
+            
+            List preparedXAWorkList = getXAWork(globalId, toRecoverMap);
+            if (heurData == null)
+            {
+               txManager.recreateTransaction(data.localTransactionId,
+                                             data.inboundFormatId,
+                                             data.globalTransactionId,
+                                             data.inboundBranchQualifier,
+                                             preparedXAWorkList,
+                                             data.resources,
+                                             completionHandler,
+                                             null);
+            }
+            else
+            {
+               // locally-detected heuristic hazard
+               if (heurData.transactionStatus == Status.STATUS_COMMITTING)
+               {
+                  List pendingXAWorkList = commitXAWork(globalId, toRecoverMap);
+                  txManager.recreateTransaction(data.localTransactionId,
+                                                data.inboundFormatId,
+                                                data.globalTransactionId,
+                                                data.inboundBranchQualifier,
+                                                pendingXAWorkList,
+                                                data.resources,
+                                                completionHandler,
+                                                heurData);
+               }
+               else if (heurData.transactionStatus == Status.STATUS_ROLLING_BACK)
+               {
+                  List pendingXAWorkList = 
+                     rollbackXAWork(globalId, toRecoverMap);
+                  txManager.recreateTransaction(data.localTransactionId,
+                                                data.inboundFormatId,
+                                                data.globalTransactionId,
+                                                data.inboundBranchQualifier,
+                                                pendingXAWorkList,
+                                                data.resources,
+                                                completionHandler,
+                                                heurData);
+               }
+               else
+               {
+                  log.warn("Cannot recover tx=" + toString() + 
+                           "\nInconsistent state", 
+                           new Throwable("[Stack trace]"));
+               }
+            }
+         }
+      }
+   }
+
+   /**
+    * Commits the XA work associated with a given global transaction id. This
+    * method receives a <code>toRecoverMap</code> whose values are 
+    * <code>XAResourceXids<code> objects that contain the <code>Xids</code>
+    * of all active XA transaction branches that require recovery actions. 
+    * It removes from the <code>toRecoverMap</code> all <code>Xids</code> 
+    * that correspond to the XA work committed (those associated with the
+    * specified <code>globalId</code>).
+    * 
+    * @param globalId the global transaction id associated with the work to 
+    *                 commit
+    * @param toRecoverMap a map whose keys are <code>Recoverable</code> ids and
+    *                     whose values are <code>XAResourceXids<code> objects,
+    *                     each of which contains the set of <code>Xids</code>
+    *                     for all active XA transaction branches that involve 
+    *                     a given <code>XAResource<code> and that require 
+    *                     recovery actions
+    * @return a "pending work" list containing <code>XAWork</code> instances 
+    *         describing the work that should have been committed, but could 
+    *         not be committed due to transient problems. The caller should 
+    *         try to commit the pending work again, at a later time.
+    */
+   private List commitXAWork(byte[] globalId, Map toRecoverMap) 
+   {
+      log.info("*** trying to complete XA work with globalId " +
+               new String(globalId).trim());
+      globalId = pad(globalId);
+      List pendingXAWorkList = new ArrayList();
+      Iterator rit = toRecoverMap.values().iterator();
+      while (rit.hasNext())
+      {
+         XAResourceXids toRecover = (XAResourceXids) rit.next();
+         log.info("    looking at resource " + toRecover.recoverable.getId());
+
+         Iterator resXidIt = toRecover.xids.iterator();
+         while (resXidIt.hasNext())
+         {
+            Xid resXid = (Xid) resXidIt.next();
+            byte[] resGlobalId = pad(resXid.getGlobalTransactionId());
+            if (!Arrays.equals(globalId, resGlobalId))
+               continue;
+            try
+            {
+               toRecover.resource.commit(resXid, false);
+               log.info("        committed: " + resXid);
+            }
+            catch (XAException e)
+            {
+               switch (e.errorCode)
+               {
+               case XAException.XA_HEURCOM:
+                  // Ignore this exception, as the the heuristic outcome
+                  // is the one we wanted anyway.
+                  log.trace("commitXAWork ignored XAException.XA_HEURCOM", e);
+                  try
+                  {
+                     toRecover.resource.forget(resXid);
+                  }
+                  catch (XAException xae)
+                  {
+                     log.warn("XAException in commitXAWork (when forgetting " +
+                              "XA_HEURCOM): errorCode=" + 
+                              TxUtils.getXAErrorCodeAsString(xae.errorCode), 
+                              xae);
+                  }
+                  break;
+               case XAException.XA_HEURRB:
+               case XAException.XA_HEURMIX:
+               case XAException.XA_HEURHAZ:
+                  log.warn("Heuristic XAException in commitXAWork: errorCode=" + 
+                           TxUtils.getXAErrorCodeAsString(e.errorCode) +
+                           "\nWill deal with the heuristic later", e);
+                  XAWork postponedWork = new XAWork(toRecover.resource, 
+                                                    resXid, 
+                                                    toRecover.resourceAccess);
+                  pendingXAWorkList.add(postponedWork);
+                  break;
+               case XAException.XAER_RMERR:
+                  log.warn("Unexpected XAException in commitXAWork: errorCode="
+                           + TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+                  break;
+               case XAException.XAER_RMFAIL:
+               case XAException.XA_RETRY:
+                  log.warn("XAException in commitXAWork: errorCode=" + 
+                           TxUtils.getXAErrorCodeAsString(e.errorCode) +
+                           "\nWill attempt to commit the XAResource later", e);
+                  XAWork pendingXAWork = new XAWork(toRecover.resource, 
+                                                    resXid,
+                                                    toRecover.resourceAccess);
+                  pendingXAWorkList.add(pendingXAWork);
+                  break;
+               case XAException.XAER_NOTA:
+               case XAException.XAER_INVAL:
+               case XAException.XAER_PROTO:
+               default:
+                  // This should never happen!
+                  log.warn("Could not recover from unexpected XAException: " +
+                           " errorCode=" + 
+                           TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+                  break;
+               }
+            }
+            finally
+            {
+               resXidIt.remove(); // remove resXid from toRecover.xids
+            }
+         }
+         if (toRecover.xids.isEmpty())
+            rit.remove(); // remove toRecover from toRecoverMap
+      }
+      return pendingXAWorkList;
+   }
+
+   private List rollbackXAWork(byte[] globalId, Map toRecoverMap)
+   {
+      log.info("*** trying to rollback XA work with globalId " +
+               new String(globalId).trim());
+      globalId = pad(globalId);
+      List pendingXAWorkList = new ArrayList();
+      Iterator rit = toRecoverMap.values().iterator();
+      while (rit.hasNext())
+      {
+         XAResourceXids toRecover = (XAResourceXids) rit.next();
+         log.info("    looking at resource " + toRecover.recoverable.getId());
+
+         Iterator resXidIt = toRecover.xids.iterator();
+         while (resXidIt.hasNext())
+         {
+            Xid resXid = (Xid) resXidIt.next();
+            byte[] resGlobalId = pad(resXid.getGlobalTransactionId());
+            if (!Arrays.equals(globalId, resGlobalId))
+               continue;
+            try
+            {
+               toRecover.resource.rollback(resXid);
+               log.info("        rolledback: " + resXid);
+            }
+            catch (XAException e)
+            {
+               switch (e.errorCode)
+               {
+               case XAException.XA_HEURRB:
+                  // Ignore this exception, as the the heuristic outcome
+                  // is the one we wanted anyway.
+                  log.trace("rollbackXAWork ignored XAException.XA_HEURRB", e);
+                  try
+                  {
+                     toRecover.resource.forget(resXid);
+                  }
+                  catch (XAException xae)
+                  {
+                     log.warn("XAException in rollbackXAWork (when forgetting " 
+                              + "XA_HEURRB): errorCode=" + 
+                              TxUtils.getXAErrorCodeAsString(xae.errorCode), 
+                              xae);
+                  }
+                  break;
+               case XAException.XA_HEURCOM:
+               case XAException.XA_HEURMIX:
+               case XAException.XA_HEURHAZ:
+                  log.warn("Heuristic XAException in rollbackXAWork: errorCode=" 
+                           + TxUtils.getXAErrorCodeAsString(e.errorCode) +
+                           "\nWill deal with the heuristic later", e);
+                  XAWork postponedWork = new XAWork(toRecover.resource, 
+                                                    resXid,
+                                                    toRecover.resourceAccess);
+                  pendingXAWorkList.add(postponedWork);
+                  break;
+               case XAException.XAER_RMERR:
+                  log.warn("Unexpected XAException in rollbackXAWork: " +
+                           "errorCode="
+                           + TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+                  break;
+               case XAException.XAER_RMFAIL:
+               case XAException.XA_RETRY:
+                  log.warn("XAException in rollbackXAWork: errorCode=" + 
+                           TxUtils.getXAErrorCodeAsString(e.errorCode) +
+                           "\nWill attempt to rollback the XAResource later", e);
+                  XAWork pendingXAWork = new XAWork(toRecover.resource,
+                                                    resXid,
+                                                    toRecover.resourceAccess);
+                  pendingXAWorkList.add(pendingXAWork);
+                  break;
+               case XAException.XAER_NOTA:
+               case XAException.XAER_INVAL:
+               case XAException.XAER_PROTO:
+               default:
+                  // This should never happen!
+                  log.warn("Could not recover from unexpected XAException: " +
+                           " errorCode=" + 
+                           TxUtils.getXAErrorCodeAsString(e.errorCode), e);
+                  break;
+               }
+            }
+            finally
+            {
+               resXidIt.remove(); // remove resXid from toRecover.xids
+            }
+         }
+         if (toRecover.xids.isEmpty())
+            rit.remove(); // remove toRecover from toRecoverMap
+      }
+      return pendingXAWorkList;
+   }
+   
+   /**
+    * Extracts from a <code>toRecoverMap</code> all the XA work that is
+    * associated with a given global transaction id. This method scans
+    * the <code>toRecoverMap</code> and builds a list of <code>XAWork</code>
+    * instances whose <code>Xids</code> contain the specified global id.
+    * It removes all those <code>Xids</code> from the the 
+    * <code>toRecoverMap</code>. 
+    *  
+    * @param globalId the global transaction id
+    * @param toRecoverMap a map whose keys are <code>Recoverable</code> ids 
+    *                     and whose values are <code>XAResourceXids<code> 
+    *                     objects, each of which contains the set of 
+    *                     <code>Xids</code> for the active XA transaction 
+    *                     branches that involve a given <code>XAResource<code>
+    *                     and that require recovery actions.
+    * @return a <code>List</code> of <code>XAWork</code> instances with 
+    *         <code>Xid</code> fields that were taken from the 
+    *         <code>toRecoverMap</code> and that contain the specified global 
+    *         transaction id.
+    */
+   private List getXAWork(byte[] globalId, Map toRecoverMap)
+   {
+      log.info("*** getting XA work with globalId " + 
+               new String(globalId).trim());
+      globalId = pad(globalId);
+      List xaWorkList = new ArrayList();
+      Iterator rit = toRecoverMap.values().iterator();
+      while (rit.hasNext())
+      {
+         XAResourceXids toRecover = (XAResourceXids) rit.next();
+         log.info("    looking at resource " + toRecover.recoverable.getId());
+
+         Iterator resXidIt = toRecover.xids.iterator();
+         while (resXidIt.hasNext())
+         {
+            Xid resXid = (Xid) resXidIt.next();
+            byte[] resGlobalId = pad(resXid.getGlobalTransactionId());
+            if (!Arrays.equals(globalId, resGlobalId))
+               continue;
+
+            XAWork preparedXAWork = new XAWork(toRecover.resource,
+                                               resXid,
+                                               toRecover.resourceAccess);
+            xaWorkList.add(preparedXAWork);
+            resXidIt.remove(); // remove resXid from toRecover.xids
+         }
+      }
+      return xaWorkList;
+   }
+
+   /**
+    * Takes an iterator for a collection of <code>XAResourceXids</code> 
+    * instances and cleans up every <code>Recoverable</code> in the 
+    * <code>recoverable</code> field of an element of the collection.
+    */
+   private void cleanupRecoverables(Iterator it)
+   {
+      while (it.hasNext())
+      {
+         XAResourceXids xaResXids = (XAResourceXids) it.next();
+         try
+         {
+            xaResXids.resourceAccess.release();
+         }
+         catch (Exception ignored)
+         {
+         }
+      }
+   }
+
+   /**
+    * Filters out every xid whose branch qualifier field was not generated by
+    * transaction manager. This is to avoid rolling back transaction branches
+    * that are not ours.
+    */
+   private List pruneXidList(Xid[] xids, 
+                             Set branchQualifiers,
+                             String resourceName)
+   {
+      ArrayList list = new ArrayList();
+      for (int i = 0; i < xids.length; i++)
+      {
+         byte[] branchQual = xids[i].getBranchQualifier();
+         String baseBranchQual = xidFactory.getBaseBranchQualifier(branchQual);
+         if (branchQualifiers.contains(baseBranchQual)) 
+         {
+            list.add(xids[i]);
+            log.info("Adding xid " + xidFactory.toString(xids[i]) +
+                     " to pruned Xid list for " + resourceName);
+                     
+         }
+      }
+      return list;
+   }
+
+   /**
+    * Pads a byte array with null bytes so that the length of the padded
+    * array is <code>Xid.MAXGTRIDSIZE</code>. Called before comparing 
+    * global transaction ids.
+    * 
+    */ 
+   private byte[] pad(byte[] globalId)
+   {
+      if (globalId.length < Xid.MAXGTRIDSIZE)
+      {
+         byte[] bytes = new byte[Xid.MAXGTRIDSIZE];
+         System.arraycopy(globalId, 0, bytes, 0, globalId.length);
+         globalId = bytes;
+      }
+      return globalId;
+   }
+   
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerService.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerService.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerService.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,183 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import java.util.ArrayList;
+
+import javax.management.Notification;
+import javax.management.NotificationFilter;
+import javax.management.NotificationListener;
+import javax.management.ObjectName;
+
+import org.jboss.system.ServiceMBeanSupport;
+import org.jboss.system.server.Server;
+import org.jboss.system.server.ServerImplMBean;
+import org.jboss.tm.TxManager;
+import org.jboss.tm.XidFactoryMBean;
+
+/**
+ * Service MBean that manages crash recovery.
+ * 
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public class RecoveryManagerService 
+      extends ServiceMBeanSupport 
+      implements NotificationListener, 
+                 RecoveryManagerServiceMBean
+{
+   private ObjectName xidFactory;
+   private ObjectName txManager;
+   private RecoveryLogger recoveryLogger;
+   private XidFactoryMBean xidFactoryObj;
+   private TxManager txManagerObj;
+   private ArrayList xaResourceManagers = new ArrayList();
+
+   // TODO make pluggable
+   private RecoveryManager recoveryManager;
+
+   // ServiceMBeanSupport overrides ---------------------------------
+
+   /**
+    * @see org.jboss.system.ServiceMBeanSupport#startService()
+    */   
+   protected void startService() throws Exception
+   {
+      super.startService();
+      xidFactoryObj =
+         (XidFactoryMBean) getServer().getAttribute(xidFactory, "Instance");
+      txManagerObj = 
+         (TxManager) getServer().getAttribute(txManager, "TransactionManager");
+      txManagerObj.setRecoveryLogger(recoveryLogger);
+
+      NotificationFilter filter = new NotificationFilter()
+      {
+         private static final long serialVersionUID = 1L;
+
+         public boolean isNotificationEnabled(Notification n)
+         {
+            return n.getType().equals(Server.START_NOTIFICATION_TYPE);
+         }
+      };
+
+      this.getServer().addNotificationListener(ServerImplMBean.OBJECT_NAME, 
+                                               this, 
+                                               filter, 
+                                               null);
+   }
+
+   /**
+    * @see org.jboss.system.ServiceMBeanSupport#stopService()
+    */
+   protected void stopService() throws Exception
+   {
+      super.stopService();
+   }
+
+   // NotificationListener implementation ---------------------------
+
+   /**
+    * @see javax.management.NotificationListener#handleNotification(
+    *                           javax.management.Notification, java.lang.Object)
+    */
+   public void handleNotification(Notification notification, Object handback)
+   {
+      log.info("RECEIVED STARTUP NOTIFICATION");
+      if (/* resources.size() > 0 && */ recoveryLogger != null)
+      {
+         recover();
+      }
+      txManagerObj.clearRecoveryPending();
+   }
+
+   // RecoveryManagerServiceMBean implementation --------------------
+
+   /**
+    * @see org.jboss.tm.recovery.RecoveryManagerServiceMBean#getXidFactory()
+    */
+   public ObjectName getXidFactory()
+   {
+      return xidFactory;
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.RecoveryManagerServiceMBean#setXidFactory(
+    *                                               javax.management.ObjectName)
+    */
+   public void setXidFactory(ObjectName xidFactory)
+   {
+      this.xidFactory = xidFactory;
+   }
+   
+   /**
+    * @see org.jboss.tm.recovery.RecoveryManagerServiceMBean#getTransactionManager()
+    */
+   public ObjectName getTransactionManager()
+   {
+      return txManager;
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.RecoveryManagerServiceMBean#setTransactionManager(
+    *                                               javax.management.ObjectName)
+    */
+   public void setTransactionManager(ObjectName txManager)
+   {
+      this.txManager = txManager;
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.RecoveryManagerServiceMBean#setRecoveryLogger(
+    *                              org.jboss.tm.recovery.RecoveryLoggerInstance)
+    */
+   public void setRecoveryLogger(RecoveryLoggerInstance recoveryLogger)
+   {
+      this.recoveryLogger = recoveryLogger.getInstance();
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.RecoveryManagerServiceMBean#recover()
+    */
+   public void recover()
+   {
+      try
+      {
+         recoveryManager = 
+            new RecoveryManager(xidFactoryObj, txManagerObj, recoveryLogger);
+         recoveryManager.recover(xaResourceManagers);
+      }
+      catch (Exception e)
+      {
+         log.error("Unable to recover", e);
+      }
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.RecoveryManagerServiceMBean#registerRecoverable(
+    *                                         org.jboss.tm.recovery.Recoverable)
+    */
+   public void registerRecoverable(Recoverable recoverable)
+   {
+      xaResourceManagers.add(recoverable);
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerServiceMBean.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerServiceMBean.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryManagerServiceMBean.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,84 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import javax.management.ObjectName;
+
+import org.jboss.system.ServiceMBean;
+
+/**
+ * MBean interface of the recovery manager service.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public interface RecoveryManagerServiceMBean 
+      extends ServiceMBean
+{
+   /**
+    * Gets the Xid factory's object name.
+    * 
+    * @return the Xid factory's object name.
+    */
+   ObjectName getXidFactory();
+
+   /**
+    * Sets the Xid factory's object name.
+    * 
+    * @param xidFactory the Xid factory's object name.
+    */
+   void setXidFactory(ObjectName xidFactory);
+
+   /**
+    * Gets the transaction manager's object name.
+    * 
+    * @return the transaction manager's object name.
+    */
+   ObjectName getTransactionManager();
+
+   /**
+    * Sets the transaction manager's object name.
+    * 
+    * @param txManager the transaction manager's object name.
+    */
+   void setTransactionManager(ObjectName txManager);
+
+   /**
+    * Sets the recovery logger.
+    * 
+    * @param recoveryLogger a <code>RecoveryLoggerInstance</code>.
+    */
+   void setRecoveryLogger(RecoveryLoggerInstance recoveryLogger);
+
+   /**
+    * Registers a <code>Recoverable</code> instance with the recovery manager 
+    * service. 
+    *  
+    * @param recoverable the <code>Recoverable</code> instance to be registered.
+    */
+   void registerRecoverable(Recoverable recoverable);
+
+   /**
+    * Performs crash recovery.
+    */
+   void recover();
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryTestingException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryTestingException.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/RecoveryTestingException.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,53 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+/**
+ * This exception is used by the recovery testing framework to force the TM to abort at
+ * certain points so that recovery logging can be tested.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public class RecoveryTestingException extends RuntimeException
+{
+   private static final long serialVersionUID = 1L;
+
+   public RecoveryTestingException()
+   {
+   }
+
+   public RecoveryTestingException(String message)
+   {
+      super(message);
+   }
+
+   public RecoveryTestingException(String message, Throwable cause)
+   {
+      super(message, cause);
+   }
+
+   public RecoveryTestingException(Throwable cause)
+   {
+      super(cause);
+   }
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/SimpleHeuristicStatusLogReader.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/SimpleHeuristicStatusLogReader.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/SimpleHeuristicStatusLogReader.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,155 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.FileChannel;
+import java.util.Map;
+
+/**
+ * Simple implementation of <code>HeuristicStatusLogReader</code> used at 
+ * recovery time. The <code>BatchRecoveryLogger</code>'s implementation of 
+ * method <code>getHeuristicStatusLogs()</code> instantiates 
+ * <code>SimpleHeuristicStatusLogReader</code>s for the existing heuristic
+ * status log files. It returns an array containing those readers, which the 
+ * recovery manager uses to get information on heuristically completed
+ * transactions.
+ * 
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class SimpleHeuristicStatusLogReader 
+   implements HeuristicStatusLogReader
+{
+   /** The underlying heuristic status log file. */
+   private File logFile;
+   
+   /**
+    * Constructs a <code>SimpleHeuristicStatusLogReader</code>.
+    * 
+    * @param logFile the heuristic status log file to read.
+    */
+   public SimpleHeuristicStatusLogReader(File logFile)
+   {
+      this.logFile = logFile;
+   }
+
+   /**
+    * Gets the name of the heuristic status log file.
+    * 
+    * @return the name of the heuristic status log file.
+    */
+   public String getLogFileName()
+   {
+      return logFile.toString();
+   }
+
+   /**
+    * Recovers information on heuristically completed transactions from 
+    * the heuristic status log file.
+    *
+    * @param heuristicallyCompletedTransactions a <code>Map</code> to which
+    *              this method will one entry per heuristically completed
+    *              transaction. The map keys are <code>Long</code> values 
+    *              containing local transaction ids. The map values are
+    *              <code>LogRecord.HeurData</code> objects with information
+    *              on heuristically completed transactions.
+    */ 
+   public void recover(Map heuristicallyCompletedTransactions)
+   {
+      FileInputStream fis;
+
+      try
+      {
+         fis = new FileInputStream(logFile);
+
+      }
+      catch (IOException e)
+      {
+         throw new RuntimeException(e);
+      }
+
+      try
+      {
+         if (fis.available() < LogRecord.FULL_HEADER_LEN)
+            return; // TODO: perhaps thrown an exception in this case?
+         
+         FileChannel channel = fis.getChannel();
+         ByteBuffer buf = ByteBuffer.allocate(LogRecord.FULL_HEADER_LEN);
+         channel.read(buf);
+         
+         int len = LogRecord.getNextRecordLength(buf, 0);
+         LogRecord.HeurData data = new LogRecord.HeurData();
+         
+         while (len > 0)
+         {
+            buf = ByteBuffer.allocate(len + LogRecord.FULL_HEADER_LEN); 
+            if (channel.read(buf) < len)
+               break; // TODO: throw an exception or log something
+            buf.flip();
+            LogRecord.getHeurData(buf, len, data);
+            switch (data.recordType)
+            {
+            case LogRecord.HEUR_STATUS:
+               heuristicallyCompletedTransactions.put(
+                                             new Long(data.localTransactionId), 
+                                             data);
+               break;
+               
+            case LogRecord.HEUR_FORGOTTEN:
+               heuristicallyCompletedTransactions.remove(
+                                             new Long(data.localTransactionId));
+               break;
+               
+            default:
+               // TODO: log something
+               break;
+            }
+            len = LogRecord.getNextRecordLength(buf, len);
+         }
+      }
+      catch (IOException ignore)
+      {
+      }
+      try
+      {
+         fis.close();
+      }
+      catch (IOException e)
+      {
+         throw new RuntimeException(e);
+      }
+
+   }
+
+   /**
+    * Removes the heuristic status log file.
+    */
+   public void finishRecovery()
+   {
+      logFile.delete();
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/TransactionCompletionLogger.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/TransactionCompletionLogger.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/TransactionCompletionLogger.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,73 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This <code>TxCompletionHandler</code> implementation writes 
+ * <code>TX_END</code> records to a <code>BatchLog</code>. At transaction
+ * completion, a <code>TX_END</code> record is written out to the 
+ * <code>BatchLog</code>.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+class TransactionCompletionLogger implements TxCompletionHandler
+{
+   /** The underlying <code>BatchLog</code> instance. */
+   private BatchLog log;
+   
+   /**
+    * Constructs a <code>TransactionCompletionLogger</code> that writes
+    * <code>TX_END</code> records to a given <code>BatchLog</code>.
+    * 
+    * @param log the <code>BatchLog</code> to which <code>TX_END</code> 
+    *            records will be written out. 
+    */
+   TransactionCompletionLogger(BatchLog log)
+   {
+      this.log = log;
+   }
+
+   /**
+    * Signals the end of the two-phase commit protocol for a committed 
+    * transaction. This method should be invoked when the second phase of the 
+    * two-phase commit protocol completes successfully and no heuristic
+    * decisions were made. In the case of a heuristically committed transaction,
+    * this method should not be invoked at the end of the second phase of the
+    * two-phase commit protocol. Instead, the <code>handleTxCompletion</code> 
+    * call should be postponed until the heuristic outcome is forgotten.
+    * 
+    * This implementation writes to the <code>BatchLog</code> a 
+    * <code>TX_END</code> record for the transaction.
+    *
+    * @param localTransactionId the local id of the completed transaction.
+    */
+   public void handleTxCompletion(long localTransactionId)
+   {
+      ByteBuffer buffer = LogRecord.createTxEndRecord(localTransactionId);
+      BatchWriter writer = log.getBatchWriter();
+      writer.addBatch(buffer, log);
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/TxCompletionHandler.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/TxCompletionHandler.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/TxCompletionHandler.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,46 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+/**
+ * Interface of an object that should be invoked at transaction completion.
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface TxCompletionHandler
+{
+   
+   /**
+    * Signals the end of the two-phase commit protocol for a committed 
+    * transaction. This method should invoked when the second phase of the 
+    * two-phase commit protocol completes successfully and no heuristic
+    * decisions were made. In the case of a heuristically committed transaction,
+    * this method should not be invoked at the end of the second phase of the
+    * two-phase commit protocol. Instead, the <code>handleTxCompletion</code> 
+    * call should be postponed until the heuristic outcome is forgotten.
+    *
+    * @param localTransactionId the local id of the completed transaction.
+    */
+   void handleTxCompletion(long localTransactionId);
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/XAResourceAccess.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/XAResourceAccess.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/XAResourceAccess.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,40 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+/**
+ * Interface with a single method for releasing the access to an
+ * <code>XAResource</code>. The holder of an <code>XAResourceAccess<code>
+ * instance has access to some <code>XAResource</code> through a connection
+ * to an XA datasource. When it finishes using the <code>XAResource</code>,  
+ * it must call <code>release</code> on the <code>XAResourceAccess<code> 
+ * instance. If no other object has access to the <code>XAResource</code>,
+ * then the <code>relase</code> call closes the underlying 
+ * <code>XAConnection</code>. 
+ * 
+ * @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface XAResourceAccess
+{
+   void release();
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/XAWork.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/XAWork.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/XAWork.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,64 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+
+/**
+ * "Struct class" that represents the work to be performed by a given XA 
+ * resource for a given transaction branch. 
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class XAWork
+{
+   /** The XAResource. */
+   public XAResource res;
+
+   /** The transaction branch identifier. */
+   public Xid xid;
+   
+   /** The XAResourceAccess to be released when the work is completed. */
+   public XAResourceAccess xaResourceAccess;
+   
+   /**
+    * Constructs an <code>XAWork</code> instance given an
+    * <code>XAResource</code>, a transaction branch identifier, and 
+    * an <code>XAResourceAccess</code> instance. 
+    *
+    * @param res the <code>XAResource</code>
+    * @param xid the transaction branch identifier.
+    * @param xaResourceAccess the <code>XAResourceAccess</code> instance
+    *        to be released when the work is completed.
+    */
+   XAWork(XAResource res, 
+                 Xid xid, 
+                 RecoveryManager.XAResourceAccessImpl xaResourceAccess)
+   {
+      this.res = res;
+      this.xid = xid;
+      this.xaResourceAccess = xaResourceAccess.duplicate();
+   }
+   
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationService.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationService.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationService.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,166 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.util.zip.Adler32;
+import java.util.zip.Checksum;
+
+import javax.management.ObjectName;
+
+import org.jboss.system.ServiceMBeanSupport;
+import org.jboss.tm.LocalId;
+import org.jboss.tm.XidFactoryMBean;
+
+/**
+ * MBean service that distinguishes "transaction generations", which correspond
+ * to different executions of a JBoss server, by stuffing a "transaction
+ * generation number" into the high part of all the local transaction ids 
+ * created by an execution of the server. This service keeps the next 
+ * transaction generation number (the value that the next server run will stuff
+ * into the high part of its local transaction ids) in a file, which it reads 
+ * and updates at server startup time.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class XidFactoryInitializationService 
+      extends ServiceMBeanSupport
+      implements XidFactoryInitializationServiceMBean
+{
+   private ObjectName xidFactory;
+   private String filename;
+   private int nextTxGenerationNumber = 0;
+
+   // ServiceMBeanSupport override ----------------------------------
+
+   /**
+    * @see org.jboss.system.ServiceMBeanSupport#startService()
+    */
+   protected void startService()
+      throws Exception
+   {
+      super.startService();
+      XidFactoryMBean xidFactoryObj = 
+         (XidFactoryMBean) getServer().getAttribute(xidFactory, "Instance");
+      
+      File nextTxGenerationFile = new File(filename);
+      nextTxGenerationFile = nextTxGenerationFile.getAbsoluteFile();
+      if (!nextTxGenerationFile.createNewFile())
+      {
+         // Read existing file.
+         InputStream in = new FileInputStream(nextTxGenerationFile);
+         DataInputStream dataIn = new DataInputStream(in);
+         int txGenNumberFromFile = dataIn.readInt();
+         int checksumFromFile = dataIn.readInt();
+         dataIn.close();
+
+         // Verify the checksum.
+         ByteBuffer buffer = ByteBuffer.allocate(4);
+         buffer.putInt(txGenNumberFromFile);
+         Checksum checksum = new Adler32();
+         checksum.update(buffer.array(), 0, 4);
+         if ((int) checksum.getValue() != checksumFromFile)
+            throw new RuntimeException("Incorrect checksum in file " +
+                                       nextTxGenerationFile + ". Could not " +
+                                       "obtain the next transaction " +
+                                       "generation number.");
+
+         // Rename existing file
+         File backupFile = new File(filename + ".bak");
+         backupFile.delete();
+         nextTxGenerationFile.renameTo(backupFile);
+         nextTxGenerationNumber = txGenNumberFromFile;
+      }
+      
+      // Set the transaction generation in the Xid factory. 
+      xidFactoryObj.setGlobalIdNumber(LocalId.assemble(nextTxGenerationNumber, 
+                                                       0 /* tx number */));
+      
+      // Increment and save the next transaction generation number. 
+      nextTxGenerationNumber++;
+      ByteBuffer buffer = ByteBuffer.allocate(4);
+      buffer.putInt(nextTxGenerationNumber);
+      Checksum checksum = new Adler32();
+      checksum.update(buffer.array(), 0, 4);
+
+      FileOutputStream out= new FileOutputStream(nextTxGenerationFile);
+      DataOutputStream dataOut = new DataOutputStream(out);
+      dataOut.writeInt(nextTxGenerationNumber);
+      dataOut.writeInt((int) checksum.getValue());
+      dataOut.flush();
+      out.getFD().sync();
+      dataOut.close();
+   }
+   
+   // XidFactoryInitializationServiceMBean implementation -----------
+
+   /**
+    * @see org.jboss.tm.recovery.XidFactoryInitializationServiceMBean#getXidFactory()
+    */
+   public ObjectName getXidFactory()
+   {
+      return xidFactory;
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.XidFactoryInitializationServiceMBean#setXidFactory(
+    *                                               javax.management.ObjectName)
+    */
+   public void setXidFactory(ObjectName xidFactory)
+   {
+      this.xidFactory = xidFactory;
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.XidFactoryInitializationServiceMBean#getNextTxGenerationFile()
+    */
+   public String getNextTxGenerationFile()
+   {
+      return filename;
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.XidFactoryInitializationServiceMBean#setNextTxGenerationFile(
+    *                                                          java.lang.String)
+    */
+   public void setNextTxGenerationFile(String filename)
+   {
+      this.filename = filename;
+   }
+
+   /**
+    * @see org.jboss.tm.recovery.XidFactoryInitializationServiceMBean#getNextTxGenerationNumber()
+    */
+   public int getNextTxGenerationNumber()
+   {
+      return nextTxGenerationNumber;
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationServiceMBean.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationServiceMBean.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/XidFactoryInitializationServiceMBean.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,53 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery;
+
+import javax.management.ObjectName;
+
+import org.jboss.system.ServiceMBean;
+
+/**
+ * MBean interface of a service that distinguishes "transaction generations",
+ * which correspond to different executions of a JBoss server, by stuffing  
+ * a "transaction generation number" into the high part of all the local 
+ * transaction ids created by an execution of the server. This service keeps 
+ * the next transaction generation number (the value that the next server run
+ * will stuff into the high part of its local transaction ids) in a file, which
+ * it reads and updates at server startup time.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public interface XidFactoryInitializationServiceMBean
+      extends ServiceMBean
+{
+   ObjectName getXidFactory();
+   
+   void setXidFactory(ObjectName xidFactory);
+
+   String getNextTxGenerationFile();
+   
+   void setNextTxGenerationFile(String filename);
+   
+   int getNextTxGenerationNumber();
+   
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/test/TestForceTime.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/test/TestForceTime.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/test/TestForceTime.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,299 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery.test;
+
+import org.jboss.tm.recovery.BatchRecoveryLogger;
+import org.jboss.tm.recovery.BatchWriter;
+import org.jboss.tm.recovery.RecoveryLogTerminator;
+import org.jboss.tm.XidFactory;
+
+import java.io.RandomAccessFile;
+import java.io.IOException;
+import java.io.File;
+import java.nio.channels.FileChannel;
+import java.nio.ByteBuffer;
+
+import EDU.oswego.cs.dl.util.concurrent.Latch;
+
+import javax.transaction.xa.Xid;
+
+/**
+ * benches file per thread vs. batch queue
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public class TestForceTime
+{
+   private static final int RECORD_SIZE = Xid.MAXGTRIDSIZE * 2;
+   private static int NUM_DIRS = 2;
+   private static final int ITERATIONS = 100;
+   private static int numThreads = 500;
+   private static long average;
+   private static int count;
+   private static long startTime;
+   private static XidFactory factory = new XidFactory();
+   private static int tx_per_sec;
+
+
+   private static class BatchThread implements Runnable
+   {
+      private BatchRecoveryLogger logger;
+      private int id;
+
+      public BatchThread(int id, BatchRecoveryLogger logger)
+      {
+         this.logger = logger;
+         this.id = id;
+      }
+
+      public void run()
+      {
+         synchronized (startLock)
+         {
+            try
+            {
+               startLock.wait();
+            }
+            catch (InterruptedException e)
+            {
+               throw new RuntimeException(e);
+            }
+         }
+
+         try
+         {
+            //System.out.println("Starting " + id);
+
+            long start = System.currentTimeMillis();
+            for (int i = 0; i < ITERATIONS; i++)
+            {
+               Xid xid = factory.newXid();
+               RecoveryLogTerminator term = logger.committing(xid);
+               term.committed(xid);
+            }
+            long time = System.currentTimeMillis() - start;
+            //System.out.println(ITERATIONS + " batches took : " + time + " of id " + id);
+            stats("batch: ", time);
+         }
+         catch (Exception e)
+         {
+            e.printStackTrace();
+         }
+      }
+
+   }
+
+   private static void stats(String tag, long time)
+   {
+      synchronized (startLock)
+      {
+         count++;
+         if (count == numThreads)
+         {
+            long end = System.currentTimeMillis() - startTime;
+            System.out.println(tag + " TOTAL TIME TOOK: " + end);
+            tx_per_sec = (int) (((double) (numThreads * ITERATIONS) / (double) end) * 1000);
+            System.out.println("tx per sec = " + tx_per_sec);
+            double avg = (double) average / (double) numThreads;
+            //System.out.println(tag + "average time took: " + avg);
+         }
+         average += time;
+      }
+   }
+
+   public static void main(String[] args) throws Exception
+   {
+
+
+      for (int i = 1; i <= 1024; i = i * 2)
+      {
+         System.out.println("*** threads: " + i);
+         NUM_DIRS = 1;
+         numThreads = i;
+         average = 0;
+         count = 0;
+         runLogger();
+      }
+
+
+      /*
+      int i = 1024;
+      int last_tx = 0;
+      while (true)
+      {
+         System.out.println("*** threads: " + i);
+         NUM_DIRS = 1;
+         numThreads = i;
+         average = 0;
+         count = 0;
+         runLogger();
+         if (last_tx > tx_per_sec) break;
+         last_tx = tx_per_sec;
+         i+= 1;
+      }
+      */
+   }
+
+   private static void runLogger()
+           throws IOException, InterruptedException
+   {
+      average = 0;
+      File dir = new File("/tmp/batchRecovery");
+      dir.mkdirs();
+      String[] dirs = new String[NUM_DIRS];
+      for (int i = 0; i < dirs.length; i++)
+      {
+         dirs[i] = "/tmp/batchRecovery/dir" + i;
+      }
+      BatchRecoveryLogger logger = new BatchRecoveryLogger();
+      logger.setDirectoryList(dirs);
+      logger.setMaxLogSize(ITERATIONS * numThreads);
+      try
+      {
+         logger.start();
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException(e);
+      }
+
+      Thread[] workers = new Thread[numThreads];
+
+      for (int i = 0; i < numThreads; i++)
+      {
+         workers[i] = new Thread(new BatchThread(i, logger));
+         workers[i].start();
+      }
+
+      Thread.sleep(1000);
+
+      //System.out.println("waking up threads");
+      count = 0;
+      startTime = System.currentTimeMillis();
+
+      synchronized (startLock)
+      {
+         startLock.notifyAll();
+      }
+
+      for (int i = 0; i < numThreads; i++)
+      {
+         workers[i].join();
+      }
+
+      //System.out.println("logger.stop");
+      try
+      {
+         logger.stop();
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException(e);
+      }
+   }
+
+   private static void simple()
+           throws Exception
+   {
+      average = 0;
+      Thread[] workers = new Thread[numThreads];
+
+      for (int i = 0; i < numThreads; i++)
+      {
+         workers[i] = new Thread(new SimpleThread(i));
+         workers[i].start();
+      }
+
+      Thread.sleep(1000);
+
+      //System.out.println("waking up threads");
+
+      startTime = System.currentTimeMillis();
+      count = 0;
+
+      synchronized (startLock)
+      {
+         startLock.notifyAll();
+      }
+
+      for (int i = 0; i < numThreads; i++)
+      {
+         workers[i].join();
+      }
+   }
+
+   private static Object startLock = new Object();
+
+   private static class SimpleThread implements Runnable
+   {
+      private FileChannel channel;
+      private RandomAccessFile raf;
+      private int id;
+      private BatchWriter logger;
+
+      public SimpleThread(int id) throws IOException
+      {
+
+         File dir = new File("/tmp/SimpleRecovery/dir" + id);
+         dir.mkdirs();
+         logger = new BatchWriter("hello", 100, dir, RECORD_SIZE, ITERATIONS * numThreads);
+      }
+
+      public void run()
+      {
+         synchronized (startLock)
+         {
+            try
+            {
+               startLock.wait();
+            }
+            catch (InterruptedException e)
+            {
+               throw new RuntimeException(e);
+            }
+         }
+
+         //System.out.println("Starting...");
+
+         long start = System.currentTimeMillis();
+         boolean failed = false;
+         for (int i = 0; i < ITERATIONS; i++)
+         {
+            byte[] bytes = new byte[RECORD_SIZE];
+            ByteBuffer buf = ByteBuffer.wrap(bytes);
+            try
+            {
+               logger.committing(buf);
+            }
+            catch (Exception e)
+            {
+               failed = true;
+            }
+         }
+         long time = (System.currentTimeMillis() - start);
+         //System.out.println(ITERATIONS + " forces took: " + time);
+         stats("SIMPLE: " + failed + " ", time);
+         logger.cleanup();
+      }
+   }
+}

Added: trunk/transaction/src/main/org/jboss/tm/recovery/test/TestOracleXA.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/recovery/test/TestOracleXA.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/recovery/test/TestOracleXA.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,480 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.recovery.test;
+
+import java.sql.*;
+import javax.sql.*;
+
+import oracle.jdbc.*;
+import oracle.jdbc.pool.*;
+import oracle.jdbc.xa.OracleXid;
+import oracle.jdbc.xa.OracleXAException;
+import oracle.jdbc.xa.client.*;
+
+import javax.transaction.xa.*;
+
+import org.jboss.tm.XidFactory;
+import org.jboss.tm.XidImpl;
+
+
+/**
+ * Comment
+ *
+ * @author <a href="mailto:bill at jboss.org">Bill Burke</a>
+ * @version $Revision: 37459 $
+ */
+public class TestOracleXA
+{
+
+   static String URL1 = "jdbc:oracle:thin:@192.168.1.102:1521:joracle";
+   static String URL2 = "jdbc:oracle:thin:@192.168.1.102:1521:joracle";
+
+
+   public static void main(String args [])
+   {
+      try
+      {
+         boolean fail = false;
+
+
+         if (factory == null)
+         {
+            factory = new XidFactory();
+            factory.setPad(true);
+            base = (XidImpl) factory.newXid();
+         }
+
+         if (args.length > 0)
+         {
+
+            if (args[0].equals("recover"))
+            {
+               recover2();
+               return;
+            }
+            else if (args[0].equals("print"))
+            {
+               DriverManager.registerDriver(new OracleDriver());
+               printResults();
+               return;
+            }
+            else if (args[0].equals("fail"))
+            {
+               fail = true;
+            }
+            else if (args[0].equals("nonxa"))
+            {
+               DriverManager.registerDriver(new OracleDriver());
+               nonXA();
+               return;
+            }
+         }
+
+
+         DriverManager.registerDriver(new OracleDriver());
+
+         // You can put a database name after the @ sign in the connection URL.
+         Connection conna =
+         DriverManager.getConnection(URL1, "HR", "H19");
+
+         Connection connb =
+         DriverManager.getConnection(URL2, "HR", "H19");
+
+         // Prepare a statement to create the table
+         Statement stmta = conna.createStatement();
+
+         // Prepare a statement to create the table
+         Statement stmtb = connb.createStatement();
+
+         try
+         {
+            // Drop the test table
+            stmta.execute("drop table my_table");
+         }
+         catch (SQLException e)
+         {
+            System.out.println("error");
+         }
+
+         try
+         {
+            // Create a test table
+            stmta.execute("create table my_table (col1 int)");
+         }
+         catch (SQLException e)
+         {
+            System.out.println("error");
+         }
+
+         try
+         {
+            // Drop the test table
+            stmtb.execute("drop table my_tab");
+         }
+         catch (SQLException e)
+         {
+            System.out.println("error");
+         }
+
+         try
+         {
+            // Create a test table
+            stmtb.execute("create table my_tab (col1 char(30))");
+         }
+         catch (SQLException e)
+         {
+            System.out.println("error");
+         }
+
+         stmta.close();
+         stmta = null;
+         stmtb.close();
+         stmtb = null;
+
+         conna.commit();
+         conna.close();
+         conna = null;
+         connb.commit();
+         connb.close();
+         connb = null;
+
+
+
+         // Create XADataSource instances and set properties.
+         OracleXADataSource oxds1 = new OracleXADataSource();
+         oxds1.setURL(URL1);
+         oxds1.setUser("HR");
+         oxds1.setPassword("H19");
+
+         OracleXADataSource oxds2 = new OracleXADataSource();
+
+         oxds2.setURL(URL2);
+         oxds2.setUser("HR");
+         oxds2.setPassword("H19");
+
+         // Get XA connections to the underlying data sources
+         XAConnection pc1 = oxds1.getXAConnection();
+         XAConnection pc2 = oxds2.getXAConnection();
+
+         // Get the physical connections
+         Connection conn1 = pc1.getConnection();
+         Connection conn2 = pc2.getConnection();
+
+         // Get the XA resources
+         XAResource oxar1 = pc1.getXAResource();
+         XAResource oxar2 = pc2.getXAResource();
+
+         // Create the Xids With the Same Global Ids
+         Xid xid1 = createXid(1);
+         Xid xid2 = createXid(2);
+
+         // Start the Resources
+         oxar1.start(xid1, XAResource.TMNOFLAGS);
+         oxar2.start(xid2, XAResource.TMNOFLAGS);
+
+         // Execute SQL operations with conn1 and conn2
+         doSomeWork1(conn1);
+         doSomeWork2(conn2);
+
+         // END both the branches -- IMPORTANT
+         oxar1.end(xid1, XAResource.TMSUCCESS);
+         oxar2.end(xid2, XAResource.TMSUCCESS);
+
+         // Prepare the RMs
+         int prp1 = oxar1.prepare(xid1);
+         int prp2 = oxar2.prepare(xid2);
+
+         System.out.println("Return value of prepare 1 is " + prp1);
+         System.out.println("Return value of prepare 2 is " + prp2);
+
+         boolean do_commit = true;
+
+         if (!((prp1 == XAResource.XA_OK) || (prp1 == XAResource.XA_RDONLY)))
+            do_commit = false;
+
+         if (!((prp2 == XAResource.XA_OK) || (prp2 == XAResource.XA_RDONLY)))
+            do_commit = false;
+
+         System.out.println("do_commit is " + do_commit);
+         System.out.println("Is oxar1 same as oxar2 ? " + oxar1.isSameRM(oxar2));
+
+
+         if (fail)
+         {
+            System.exit(1);
+            // Close connections
+            conn1.close();
+            conn1 = null;
+            conn2.close();
+            conn2 = null;
+
+            pc1.close();
+            pc1 = null;
+            pc2.close();
+            pc2 = null;
+
+            recover2();
+         }
+
+         else
+         {
+
+            if (prp1 == XAResource.XA_OK)
+               if (do_commit)
+                  oxar1.commit(xid1, false);
+               else
+                  oxar1.rollback(xid1);
+
+            if (prp2 == XAResource.XA_OK)
+               if (do_commit)
+                  oxar2.commit(xid2, false);
+               else
+                  oxar2.rollback(xid2);
+
+            // Close connections
+            conn1.close();
+            conn1 = null;
+            conn2.close();
+            conn2 = null;
+
+            pc1.close();
+            pc1 = null;
+            pc2.close();
+            pc2 = null;
+         }
+
+
+         printResults();
+
+      }
+      catch (SQLException sqe)
+      {
+         sqe.printStackTrace();
+      }
+      catch (XAException xae)
+      {
+         if (xae instanceof OracleXAException)
+         {
+            System.out.println("XA Error is " +
+            ((OracleXAException) xae).getXAError());
+            System.out.println("SQL Error is " +
+            ((OracleXAException) xae).getOracleError());
+         }
+         else
+         {
+            xae.printStackTrace();
+            System.out.println("error code: " + xae.errorCode);
+         }
+      }
+      catch (Throwable t)
+      {
+         System.out.println("****SHIT!!!");
+         t.printStackTrace();
+      }
+      System.out.println("DONE!!!");
+   }
+
+   private static void recover2()
+   throws SQLException, XAException
+   {
+      // Create XADataSource instances and set properties.
+      OracleXADataSource oxds1 = new OracleXADataSource();
+      oxds1.setURL(URL1);
+      oxds1.setUser("HR");
+      oxds1.setPassword("H19");
+
+      OracleXADataSource oxds2 = new OracleXADataSource();
+
+      oxds2.setURL(URL2);
+      oxds2.setUser("HR");
+      oxds2.setPassword("H19");
+
+      XAConnection pc1;
+      XAConnection pc2;
+      Connection conn1;
+      Connection conn2;
+      XAResource oxar1;
+      XAResource oxar2;
+      // TRY RECOVERING
+
+      // Get XA connections to the underlying data sources
+      pc1 = oxds1.getXAConnection();
+      pc2 = oxds2.getXAConnection();
+
+      // Get the physical connections
+      conn1 = pc1.getConnection();
+      conn2 = pc2.getConnection();
+
+      // Get the XA resources
+      oxar1 = pc1.getXAResource();
+      oxar2 = pc2.getXAResource();
+
+      System.out.println("*** TRYING TO RECOVER");
+
+      Xid[] recover1 = oxar1.recover(XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN);
+      if (recover1 != null)
+      {
+         System.out.println("RECOVERING 1");
+         for (int i = 0; i < recover1.length; i++)
+         {
+            Xid xid = factory.fromXid(recover1[i]);
+            System.out.println("recovering XID: " + xid);
+            oxar1.commit(recover1[i], false);
+         }
+      }
+      else
+      {
+         System.out.println("RECOVER1 returned null");
+      }
+
+      Xid[] recover2 = oxar2.recover(XAResource.TMSTARTRSCAN | XAResource.TMENDRSCAN);
+      if (recover2 != null)
+      {
+         System.out.println("RECOVERING 2");
+         for (int i = 0; i < recover2.length; i++)
+         {
+            Xid xid = factory.fromXid(recover2[i]);
+            System.out.println("recovering XID: " + xid);
+            oxar2.commit(recover2[i], false);
+         }
+      }
+      else
+      {
+         System.out.println("RECOVER1 returned null");
+      }
+
+      System.out.println("HERE!!!!!");
+
+      // Close connections
+      conn1.close();
+      conn1 = null;
+      conn2.close();
+      conn2 = null;
+
+      pc1.close();
+      pc1 = null;
+      pc2.close();
+      pc2 = null;
+   }
+
+   private static void printResults()
+   throws SQLException
+   {
+      Connection conna;
+      Connection connb;
+      Statement stmta;
+      Statement stmtb;
+      conna =
+      DriverManager.getConnection(URL1, "HR", "H19");
+
+      connb =
+      DriverManager.getConnection(URL2, "HR", "H19");
+
+      // Prepare a statement to create the table
+      stmta = conna.createStatement();
+
+      // Prepare a statement to create the table
+      stmtb = connb.createStatement();
+
+      ResultSet rset = stmta.executeQuery("select col1 from my_table");
+      while (rset.next())
+         System.out.println("Col1 is " + rset.getInt(1));
+
+      rset.close();
+      rset = null;
+
+      rset = stmtb.executeQuery("select col1 from my_tab");
+      while (rset.next())
+         System.out.println("Col1 is " + rset.getString(1));
+
+      rset.close();
+      rset = null;
+
+      stmta.close();
+      stmta = null;
+      stmtb.close();
+      stmtb = null;
+
+      conna.commit();
+      conna.close();
+      conna = null;
+      connb.commit();
+      connb.close();
+      connb = null;
+   }
+
+   private static void nonXA()
+   throws SQLException
+   {
+      Connection conna;
+      Connection connb;
+      Statement stmta;
+      Statement stmtb;
+      conna =
+      DriverManager.getConnection(URL1, "HR", "H19");
+      conna.setAutoCommit(false);
+
+      connb =
+      DriverManager.getConnection(URL2, "HR", "H19");
+
+      connb.setAutoCommit(false);
+
+      doSomeWork1(conna);
+      doSomeWork2(connb);
+
+      System.exit(1);
+
+      conna.commit();
+      conna.close();
+      conna = null;
+      connb.commit();
+      connb.close();
+      connb = null;
+   }
+
+   static XidFactory factory;
+   static XidImpl base;
+
+   static Xid createXid(int bids)
+   throws XAException
+   {
+      return factory.newBranch(base, bids);
+   }
+
+   private static void doSomeWork1(Connection conn)
+   throws SQLException
+   {
+      Statement st = conn.createStatement();
+      st.executeUpdate("insert into my_table values(1)");
+      st.close();
+   }
+
+   private static void doSomeWork2(Connection conn)
+   throws SQLException
+   {
+      Statement st = conn.createStatement();
+      st.executeUpdate("insert into my_tab values('world')");
+      st.close();
+   }
+
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/ClientInvocationHandler.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/ClientInvocationHandler.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/ClientInvocationHandler.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,372 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.Method;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.jboss.remoting.CannotConnectException;
+import org.jboss.remoting.Client;
+import org.jboss.remoting.InvalidConfigurationException;
+import org.jboss.remoting.InvokerLocator;
+import org.jboss.tm.remoting.interfaces.Coordinator;
+import org.jboss.tm.remoting.interfaces.RecoveryCoordinator;
+import org.jboss.tm.remoting.interfaces.Resource;
+import org.jboss.tm.remoting.interfaces.Synchronization;
+import org.jboss.tm.remoting.interfaces.Terminator;
+import org.jboss.tm.remoting.interfaces.TransactionFactory;
+import org.jboss.tm.remoting.server.DistributedTransactionManager;
+
+/**
+ * Client-side DTM stubs are dynamic proxies that use this 
+ * <code>InvocationHandler</code> implementation.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public class ClientInvocationHandler 
+      implements InvocationHandler, Externalizable
+{
+   static final long serialVersionUID = 2253923354553253502L;
+
+   // Constants (DTM interface codes) -------------------------------
+   
+   public static final char TRANSACTION_FACTORY = 'F';
+   public static final char COORDINATOR = 'C';
+   public static final char TERMINATOR = 'T';
+   public static final char RESOURCE = 'R';
+   public static final char RECOVERY_COORDINATOR = 'V';
+   public static final char SYNCHRONIZATION = 'S';
+   
+   // Fields --------------------------------------------------------
+   
+   private char interfaceCode; 
+   private long targetObjectId;
+   private InvokerLocator[] locators;
+   private Client client; // lazily initialized by the invoke method
+   private String stringRepresentation;
+   
+   // Constructors -------------------------------------------------- 
+
+   /** No-arg constructor for externalization. */
+   public ClientInvocationHandler()
+   {
+   }
+   
+   /** 
+    * Constructs a <code>ClientInvocationHandler</code> for a target object
+    * whose id is 0, given the remote interface of the target and its  
+    * <code>InvokerLocator</code>s.
+    */
+   public ClientInvocationHandler(Class interf, InvokerLocator[] locators)
+      throws Exception
+   {
+      this(interf, 0, locators);
+   }
+
+   /** 
+    * Constructs a <code>ClientInvocationHandler</code> for a target object
+    * given the remote interface of the target, its object id, and its
+    * <code>InvokerLocator</code>s.
+    */
+   public ClientInvocationHandler(Class interf, 
+                                  long targetObjectId,
+                                  InvokerLocator[] locators)
+         throws Exception
+   {
+      this(getInterfaceCode(interf), targetObjectId, locators);
+   }
+
+   /** 
+    * Constructs a <code>ClientInvocationHandler</code> for a target object
+    * whose id is 0, given the remote interface code of the target and its 
+    * <code>InvokerLocator</code>s.
+    */
+   public ClientInvocationHandler(char interfaceCode, InvokerLocator[] locators)
+         throws Exception
+   {
+      this(interfaceCode, 0, locators);
+   }
+
+   /** 
+    * Constructs a <code>ClientInvocationHandler</code> for a target object
+    * given the remote interface code of the target, its object id, and its 
+    * <code>InvokerLocator</code>s.
+    */
+   public ClientInvocationHandler(char interfaceCode, 
+                                  long targetObjectId,
+                                  InvokerLocator[] locators) 
+         throws Exception
+   {
+      if (interfaceCode != TRANSACTION_FACTORY
+            && interfaceCode != COORDINATOR
+            && interfaceCode != TERMINATOR
+            && interfaceCode != RESOURCE
+            && interfaceCode != RECOVERY_COORDINATOR
+            && interfaceCode != SYNCHRONIZATION)
+         throw new IllegalArgumentException();
+      
+      if (locators.length == 0)
+         throw new IllegalArgumentException();
+      
+      this.interfaceCode = interfaceCode;
+      this.targetObjectId = targetObjectId;
+      this.locators = locators;
+   }
+   
+   // Utility methods -----------------------------------------------
+   
+   /** 
+    * Returns a <code>Class</code> instance representing the remote interface
+    * implemented by the dynamic proxies that use this invocation handler.  
+    */
+   Class getClientInterface() // called by RemoteInterface.toString
+   {
+      switch (interfaceCode)
+      {
+      case TRANSACTION_FACTORY:
+         return TransactionFactory.class;
+      case COORDINATOR:
+         return Coordinator.class;
+      case TERMINATOR:
+         return Terminator.class;
+      case RESOURCE:
+         return Resource.class;
+      case RECOVERY_COORDINATOR:
+         return RecoveryCoordinator.class;
+      case SYNCHRONIZATION:
+         return Synchronization.class;
+      }
+      throw new RuntimeException("Illegal value in field interfaceCode");
+   }
+   
+   /** 
+    * Takes a <code>Class</code> instance representing a DTM interface
+    * and converts is into an interface code.
+    */
+   private static char getInterfaceCode(Class interf)
+   {
+      if (interf == TransactionFactory.class)
+         return TRANSACTION_FACTORY;
+      else if (interf == Coordinator.class)
+         return COORDINATOR;
+      else if (interf == Terminator.class) 
+         return TERMINATOR;
+      else if (interf == Resource.class)
+         return RESOURCE;
+      else if (interf == RecoveryCoordinator.class)
+         return RECOVERY_COORDINATOR;
+      else if (interf == Synchronization.class)
+         return SYNCHRONIZATION;
+      else
+         throw new IllegalArgumentException("argument is not a DTM interface"); 
+   }
+
+   // InvocationHandler method --------------------------------------
+
+   /**
+    * Uses the <code>InvokerLocator</code> associated with this handler to
+    * send out an <code>Invocation</code> containing this handler's target 
+    * object id, the given method, and the given arguments.   
+    */
+   public Object invoke(Object proxy, Method method, Object[] args)
+         throws Throwable
+   {
+      Exception savedException = null;
+
+      if (method.getDeclaringClass() == Object.class)
+      {  
+         String methodName = method.getName();
+         
+         if (methodName.equals("toString"))
+            return this.toString();
+         else if (methodName.equals("hashCode"))
+            return new Integer(this.toString().hashCode());
+         else if (methodName.equals("equals"))
+            return new Boolean(this.toString().equals(args[0].toString()));
+      }
+      
+      if (client != null)
+      {
+         // Non-null client: just use it!
+         try
+         {
+            return client.invoke(new Invocation(targetObjectId, method, args));            
+         }
+         catch (CannotConnectException e)
+         {
+            client = null;
+         }
+         
+      }
+      
+      // Either client has not been initialized yet or it became null after
+      // a failed invocation attempt. Try each locator in sequence. If all
+      // of them fail, propagate the last exception up to the caller, wrapped
+      // into a RemoteException.
+      Invocation invocation = new Invocation(targetObjectId, method, args);
+      for (int i = 0; i < locators.length; i++)
+      {
+         try
+         {
+            client = new Client(locators[i],
+                                DistributedTransactionManager.SUBSYSTEM);
+            return client.invoke(invocation);
+         }
+         catch (CannotConnectException e)
+         {
+            client = null;
+            savedException = e;
+         }
+         catch (InvalidConfigurationException e)
+         {
+            client = null;
+            savedException = e;
+         }
+      }
+      throw new RemoteException(savedException.getClass().getName(), 
+                                savedException);
+   }
+
+   // Externalizable methods ----------------------------------------
+
+   /**
+    * Reads a <code>ClientInvocationHandler</code> in externalized form: 
+    * interface code, target object id, and locator URI.
+    */
+   public void readExternal(ObjectInput in) 
+         throws IOException, ClassNotFoundException
+   {
+      this.interfaceCode = in.readChar();
+      this.targetObjectId = in.readLong();
+      short len = in.readShort();
+      if (len < 1)
+         throw new IOException("ObjectInput does not contain a valid " + 
+                               "ClientInvocationHandler");
+      this.locators = new InvokerLocator[len];
+      for (int i = 0; i < len; i++)
+      {
+         this.locators[i] = new InvokerLocator(in.readUTF());
+      }
+      this.client = null;
+   }
+
+   /**
+    * Writes a <code>ClientInvocationHandler</code> in externalized form: 
+    * interface code, target object id, and locator URI.
+    */
+   public void writeExternal(ObjectOutput out) throws IOException
+   {
+      out.writeChar(interfaceCode);
+      out.writeLong(targetObjectId);
+      out.writeShort(locators.length);
+      for (int i = 0; i < locators.length; i++)
+      {
+         out.writeUTF(locators[i].getLocatorURI());
+      }
+   }
+
+   // Conversion to/from string representation 
+   
+   /**
+    * Converts a <code>ClientInvocationHandler</code> to string. These are
+    * examples of stringfied handlers:
+    * <p>
+    * <code>T3d00000004c75,socket://server4.acme.com:3873/</code><br>
+    * <code>C1b6,socket://zee.acme.com:3873/|rmi://zee.acme.com:5678/</code>
+    * <p>
+    * The handler comprises an interface code (the first character), 
+    * immediately followed by the hexadecimal representation of the target 
+    * object id (a long value), a comma (','), and a list of locator URIs 
+    * separated by vertical bars ('|'s).
+    */
+   public String toString()
+   {
+      if (stringRepresentation == null)
+         stringRepresentation = interfaceCode + 
+                                Long.toHexString(targetObjectId) + 
+                                ',' + locators[0].getLocatorURI();
+      for (int i = 1; i < locators.length; i++)
+      {
+         stringRepresentation += '|' + locators[i].getLocatorURI();
+      }
+      return stringRepresentation;
+   }
+   
+   /**
+    * Converts a stringfied handler back into a 
+    * <code>ClientInvocationHandler</code> instance.
+    */
+   public static ClientInvocationHandler fromString(String s)
+      throws Exception
+   {
+      String locatorURI;
+      InvokerLocator locator;
+
+      int oidEndIndex = s.indexOf(',');
+      if (oidEndIndex == -1)
+         throw new IllegalArgumentException();
+
+      String oidString = s.substring(1, oidEndIndex);
+      int uriStartIndex = oidEndIndex + 1;
+      int uriEndIndex = s.indexOf('|', uriStartIndex);
+      
+      if (uriEndIndex == -1)
+      {
+         // single URI
+         locatorURI = s.substring(uriStartIndex);
+         locator = new InvokerLocator(locatorURI);
+         return new ClientInvocationHandler(s.charAt(0),
+                                            Long.parseLong(oidString, 16),
+                                            new InvokerLocator[] { locator });
+      }
+      else
+      {
+         // multiple URIs separated by '|'s
+         List locatorList = new ArrayList();
+         while (uriEndIndex != -1)
+         {
+            locatorURI = s.substring(uriStartIndex, uriEndIndex);
+            locator = new InvokerLocator(locatorURI);
+            locatorList.add(locator);
+            uriStartIndex = uriEndIndex + 1;
+            uriEndIndex = s.indexOf('|', uriStartIndex);
+         }
+         locatorURI = s.substring(uriStartIndex);
+         locator = new InvokerLocator(locatorURI);
+         locatorList.add(locator);
+         InvokerLocator[] locators = 
+            (InvokerLocator[]) locatorList.toArray(new InvokerLocator[0]);
+         return new ClientInvocationHandler(s.charAt(0),
+                                            Long.parseLong(oidString, 16),
+                                            locators);
+      }
+   }
+   
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/Invocation.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/Invocation.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/Invocation.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,671 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting;
+
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.rmi.RemoteException;
+
+import javax.transaction.HeuristicCommitException;
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+
+import org.jboss.remoting.InvokerLocator;
+import org.jboss.tm.GlobalId;
+import org.jboss.tm.remoting.interfaces.Coordinator;
+import org.jboss.tm.remoting.interfaces.HeuristicHazardException;
+import org.jboss.tm.remoting.interfaces.RecoveryCoordinator;
+import org.jboss.tm.remoting.interfaces.Resource;
+import org.jboss.tm.remoting.interfaces.Status;
+import org.jboss.tm.remoting.interfaces.Synchronization;
+import org.jboss.tm.remoting.interfaces.Terminator;
+import org.jboss.tm.remoting.interfaces.TxPropagationContext;
+import org.jboss.tm.remoting.interfaces.TransactionFactory;
+import org.jboss.tm.remoting.interfaces.TransactionInactiveException;
+import org.jboss.tm.remoting.interfaces.TransactionNotPreparedException;
+import org.jboss.tm.remoting.interfaces.Vote;
+
+
+/**
+ * An instance of this class represents a method invocation on any of the
+ * remote interfaces of the transaction service, which are the interfaces
+ * defined in the package <code>org.jboss.tm.remoting.interfaces<code>.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public class Invocation
+      implements Serializable
+{
+   static final long serialVersionUID = -7256134284357215230L;
+
+   // Nested interfaces ---------------------------------------------
+
+   /**
+    * Interface of an auxiliary object that knows how to perform an invocation 
+    * to a particular method. All the nested interfaces which follow have an 
+    * Invoker-valued static final field for each method of the corresponding
+    * transaction service interface. For example, the nested interface 
+    * <code>ITransactionFactory</code> (see below) corresponds to the interface
+    * <code>org.jboss.tm.remoting.interfaces.TransactionFactory</code>. It has
+    * an Invoker-valued field <code>CREATE</code> and a method 
+    * <code>create</code>, which mirrors the similarly named method in
+    * <code>org.jboss.tm.remoting.interface.TransactionFactory</code>.
+    * 
+    * The nested interfaces below should be implemented by one or more servant
+    * objects associated with the <code>ServerInvocationHandler</code>. Note 
+    * that all the "mirror methods" in those interfaces take an additional 
+    * parameter <code>targetId</code>, which allows a single servant to 
+    * incarnate multiple target objects. At each invocation, that parameter 
+    * identifies the target object for that invocation. 
+    */
+   private static interface Invoker
+   {
+      Object invoke(Object servant, long targetId, Object[] args) 
+            throws Throwable;
+   }
+   
+   /**
+    * This interface mirrors the interface <code>TransactionFactory</code> in 
+    * <code>org.jboss.tm.remoting.interfaces</code>. Attention: method id
+    * constants (<code>M_</code>* fields) are indices to an invoker array,
+    * so they must be consecutive integers in the range 0..max_index. They also 
+    * must be unique among all mirror interfaces.  
+    */
+   public static interface ITransactionFactory
+   {
+      /** Method id for TransactionFactory.create */
+      static final int M_CREATE = 0;
+      
+      /** Method id for TransactionFactory.recreate */
+      static final int M_RECREATE = 1;
+      
+      /** Invoker for TransactionFactory.create */
+      static final Invoker CREATE = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            int timeout = ((Integer)args[0]).intValue();
+            return ((ITransactionFactory)servant).create(targetId, timeout);
+         }
+      };
+         
+      /** Invoker for TransactionFactory.recreate */
+      static final Invoker RECREATE = new Invoker()
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            TxPropagationContext tpc = (TxPropagationContext)args[0];
+            return ((ITransactionFactory)servant).recreate(targetId, tpc);
+         }
+      };
+      
+      /** Mirror method for TransactionFactory.create */
+      TxPropagationContext create(long targetId, int timeout)
+            throws RemoteException;
+      
+      /** Mirror method for TransactionFactory.recreate */
+      TxPropagationContext recreate(long targetId, TxPropagationContext tpc)
+            throws RemoteException;
+      
+   }
+   
+   /**
+    * This interface mirrors the interface <code>Coordinator</code> in 
+    * <code>org.jboss.tm.remoting.interfaces</code>. Attention: method id
+    * constants (<code>M_</code>* fields) are indices to an invoker array,
+    * so they must be consecutive integers in the range 0..max_index. They 
+    * also must to be unique among all mirror interfaces.  
+    */
+   public static interface ICoordinator
+   {
+      /** Method id for Coordinator.getStatus */
+      static final int M_GET_STATUS = 2;
+
+      /** Method id for Coordinator.isSameTransaction */
+      static final int M_IS_SAME_TRANSACTION = 3;
+      
+      /** Method id for Coordinator.hashTransaction */
+      static final int M_HASH_TRANSACTION = 4;
+      
+      /** Method id for Coordinator.registerResource */
+      static final int M_REGISTER_RESOURCE = 5;
+      
+      /** Method id for Coordinator.registerSynchronization */
+      static final int M_REGISTER_SYNCHRONIZATION = 6;
+      
+      /** Method id for Coordinator.rollbackOnly */
+      static final int M_ROLLBACK_ONLY = 7;
+      
+      /** Method id for Coordinator.getTransactionContext */
+      static final int M_GET_TRANSACTION_CONTEXT = 8;
+      
+      /** Method id for Coordinator.getTransactionId */
+      static final int M_GET_TRANSACTION_ID = 9;
+      
+      /** Invoker for Coordinator.getStatus */
+      static final Invoker GET_STATUS = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            return ((ICoordinator)servant).getStatus(targetId);
+         }
+      };
+
+      /** Invoker for Coordinator.isSameTransaction */
+      static final Invoker IS_SAME_TRANSACTION = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            Coordinator other = (Coordinator)args[0];
+            boolean retVal = 
+                  ((ICoordinator)servant).isSameTransaction(targetId, other);
+            return Boolean.valueOf(retVal);
+         }
+      };
+      
+      /** Invoker for Coordinator.hashTransaction */
+      static final Invoker HASH_TRANSACTION = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            int retVal = ((ICoordinator)servant).hashTransaction(targetId);
+            return new Integer(retVal);
+         }
+      };
+
+      /** Invoker for Coordinator.registerResource */
+      static final Invoker REGISTER_RESOURCE = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            Resource r = (Resource)args[0];
+            return ((ICoordinator)servant).registerResource(targetId, r);
+         }
+      };
+      
+      /** Invoker for Coordinator.registerSynchronization */
+      static final Invoker REGISTER_SYNCHRONIZATION = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            Synchronization sync = (Synchronization)args[0];
+            ((ICoordinator)servant).registerSynchronization(targetId, sync);
+            return null;
+         }
+      };
+      
+      /** Invoker for Coordinator.rollbackOnly */
+      static final Invoker ROLLBACK_ONLY = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            ((ICoordinator)servant).rollbackOnly(targetId);
+            return null;
+         }
+      };
+      
+      /** Invoker for Coordinator.getTransactionContext */
+      static final Invoker GET_TRANSACTION_CONTEXT = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            return ((ICoordinator)servant).getTransactionContext(targetId);
+         }
+      };
+
+      /** Invoker for Coordinator.getTransactionId */
+      static final Invoker GET_TRANSACTION_ID = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            return ((ICoordinator)servant).getTransactionId(targetId);
+         }
+      };
+
+      /** Mirror method for Coordinator.getStatus */
+      Status getStatus(long targetId)
+            throws RemoteException;
+
+      /** Mirror method for Coordinator.isSameTransaction */
+      boolean isSameTransaction(long targetId, Coordinator c)
+            throws RemoteException;
+
+      /** Mirror method for Coordinator.hashTransaction */
+      int hashTransaction(long targetId)
+            throws RemoteException;
+
+      /** Mirror method for Coordinator.registerResource */
+      RecoveryCoordinator registerResource(long targetId, Resource r)
+            throws RemoteException,
+                   TransactionInactiveException;
+
+      /** Mirror method for Coordinator.registerSynchronization */
+      void registerSynchronization(long targetId, Synchronization sync)
+            throws RemoteException,
+                   TransactionInactiveException;
+
+      /** Mirror method for Coordinator.rollbackOnly */
+      void rollbackOnly(long targetId)
+            throws RemoteException,
+                   TransactionInactiveException;
+
+      /** Mirror method for Coordinator.getTransactionContext */
+      TxPropagationContext getTransactionContext(long targetId)
+            throws RemoteException,
+                   TransactionInactiveException;
+
+      /** Mirror method for Coordinator.getTransactionId */
+      GlobalId getTransactionId(long targetId)
+            throws RemoteException;
+   }
+   
+   /**
+    * This interface mirrors the interface <code>Terminator</code> in 
+    * <code>org.jboss.tm.remoting.interfaces</code>. Attention: method id
+    * constants (<code>M_</code>* fields) are indices to an invoker array,
+    * so they must be consecutive integers in the range 0..max_index. They 
+    * also must be unique among all mirror interfaces.  
+    */
+   public static interface ITerminator
+   {
+      /** Method id for Terminator.commit */
+      static final int M_COMMIT = 10;
+      
+      /** Method id for Terminator.rollback */
+      static final int M_ROLLBACK = 11;
+      
+      /** Invoker for Terminator.commit */
+      static final Invoker COMMIT = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            boolean reportHeuristics = ((Boolean)args[0]).booleanValue();
+            ((ITerminator)servant).commit(targetId, reportHeuristics);
+            return null;
+         }
+      };
+      
+      /** Invoker for Terminator.rollback */
+      static final Invoker ROLLBACK = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            ((ITerminator)servant).rollback(targetId);
+            return null;
+         }
+      };
+      
+      /** Mirror method for Terminator.commit */
+      void commit(long targetId, boolean reportHeuristics) 
+            throws RemoteException,
+                   HeuristicMixedException, 
+                   HeuristicHazardException;
+
+      /** Mirror method for Terminator.commit */
+      void rollback(long targetId)
+            throws RemoteException;            
+   }
+   
+   /**
+    * This interface mirrors the interface <code>Resource</code> in 
+    * <code>org.jboss.tm.remoting.interfaces</code>. Attention: method id
+    * constants (<code>M_</code>* fields) are indices to an invoker array,
+    * so they must be consecutive integers in the range 0..max_index. They
+    * also must be unique among all mirror interfaces.  
+    */
+   public static interface IResource
+   {
+      /** Method id for Resource.prepare */
+      static final int M_PREPARE = 12;
+      
+      /** Method id for Resource.rollback */
+      static final int M_ROLLBACK = 13;
+
+      /** Method id for Resource.commit */
+      static final int M_COMMIT = 14;
+
+      /** Method id for Resource.commitOnePhase */
+      static final int M_COMMIT_ONE_PHASE = 15;
+
+      /** Method id for Resource.forget */
+      static final int M_FORGET = 16;
+      
+      /** Invoker for Resource.prepare */
+      static final Invoker PREPARE = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            return ((IResource)servant).prepare(targetId);
+         }
+      };
+      
+      /** Invoker for Resource.rollback */
+      static final Invoker ROLLBACK = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            ((IResource)servant).rollbackResource(targetId);
+            return null;
+         }
+      };
+      
+      /** Invoker for Resource.commit */
+      static final Invoker COMMIT = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            ((IResource)servant).commit(targetId);
+            return null;
+         }
+      };
+      
+      /** Invoker for Resource.commitOnePhase */
+      static final Invoker COMMIT_ONE_PHASE = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            ((IResource)servant).commitOnePhase(targetId);
+            return null;
+         }
+      };
+      
+      /** Invoker for Resource.forget */
+      static final Invoker FORGET = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            ((IResource)servant).forget(targetId);
+            return null;
+         }
+      };
+      
+      /** Mirror method for Resource.prepare */
+      Vote prepare(long targetId)
+            throws RemoteException,
+                   HeuristicMixedException,
+                   HeuristicHazardException;
+
+      /** Mirror method for Resource.rollback  -- its name is rollbackResource 
+       * (rather than rollback) to avoid the name clash with Terminator.rollback
+       * within the DTMServant class */
+      void rollbackResource(long targetId)
+            throws RemoteException,
+                   HeuristicCommitException,
+                   HeuristicMixedException,
+                   HeuristicHazardException;
+
+      /** Mirror method for Resource.commit */
+      void commit(long targetId)
+            throws RemoteException,
+                   TransactionNotPreparedException,
+                   HeuristicRollbackException,
+                   HeuristicMixedException,
+                   HeuristicHazardException;
+
+      /** Mirror method for Resource.commitOnePhase */
+      void commitOnePhase(long targetId)
+            throws RemoteException,
+                   HeuristicHazardException;
+
+      /** Mirror method for Resource.forget */
+      void forget(long targetId)
+            throws RemoteException;
+   }
+   
+   /**
+    * This interface mirrors the interface <code>RecoveryCoordinator</code>
+    * in <code>org.jboss.tm.remoting.interfaces</code>. Attention: method id
+    * constants (<code>M_</code>* fields) are indices to an invoker array,
+    * so they must be consecutive integers in the range 0..max_index. They
+    * also must be unique among all mirror interfaces.  
+    */
+   public static interface IRecoveryCoordinator
+   {
+      /** Method id for RecoveryCoordinator.replayCompletion */
+      static final int M_REPLAY_COMPLETION = 17; 
+
+      /** Invoker for RecoveryCoordinator.replayCompletion */
+      static final Invoker REPLAY_COMPLETION = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            Resource r = (Resource)args[0];
+            return ((IRecoveryCoordinator)servant).replayCompletion(targetId, 
+                                                                    r);
+         }
+      };
+      
+      /** Mirror method for RecoveryCoordinator.replayCompletion */
+      Status replayCompletion(long targetId, Resource r)
+            throws RemoteException,
+                   TransactionNotPreparedException;
+   }
+   
+   /**
+    * This interface mirrors the interface <code>Synchronization</code> in 
+    * <code>org.jboss.tm.remoting.interfaces</code>. Attention: method id
+    * constants (<code>M_</code>* fields) are indices to an invoker array,
+    * so they must be consecutive integers in the range 0..max_index. They
+    * also must be unique among all mirror interfaces.  
+    */
+   public static interface ISynchronization
+   {
+      /** Method id for Synchronization.beforeCompletion */
+      static final int M_BEFORE_COMPLETION = 18;
+
+      /** Method id for Synchronization.afterCompletion */
+      static final int M_AFTER_COMPLETION = 19;
+      
+      /** Invoker for Synchronization.beforeCompletion */
+      static final Invoker BEFORE_COMPLETION = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            ((ISynchronization)servant).beforeCompletion(targetId);
+            return null;
+         }
+      };
+      
+      /** Invoker for Synchronization.afterCompletion */
+      static final Invoker AFTER_COMPLETION = new Invoker() 
+      {
+         public Object invoke(Object servant, long targetId, Object[] args)
+               throws Throwable
+         {
+            ((ISynchronization)servant).afterCompletion(targetId);
+            return null;
+         }
+      };
+      
+      /** Mirror method for Synchronization.beforeCompletion */
+      void beforeCompletion(long targetId);
+      
+      /** Mirror method for Synchronization.afterCompletion */
+      void afterCompletion(long targetId);
+   }
+   
+   // Static field --------------------------------------------------
+
+   /**
+    * Array of <code>Invoker</code> instances ordered by method id.
+    * Used for efficient (non-reflective) invocation of a method, given 
+    * its id. 
+    */
+   private static final Invoker[] invokerArray = 
+   {
+         ITransactionFactory.CREATE,
+         ITransactionFactory.RECREATE,
+         ICoordinator.GET_STATUS,
+         ICoordinator.IS_SAME_TRANSACTION,
+         ICoordinator.HASH_TRANSACTION,
+         ICoordinator.REGISTER_RESOURCE,
+         ICoordinator.REGISTER_SYNCHRONIZATION,
+         ICoordinator.ROLLBACK_ONLY,
+         ICoordinator.GET_TRANSACTION_CONTEXT,
+         ICoordinator.GET_TRANSACTION_ID,
+         ITerminator.COMMIT,   
+         ITerminator.ROLLBACK,
+         IResource.PREPARE,
+         IResource.ROLLBACK,
+         IResource.COMMIT,
+         IResource.COMMIT_ONE_PHASE,
+         IResource.FORGET,
+         IRecoveryCoordinator.REPLAY_COMPLETION,
+         ISynchronization.BEFORE_COMPLETION,
+         ISynchronization.AFTER_COMPLETION
+   };
+
+   // Static method--------------------------------------------------
+
+   /**
+    * Return the id of a given method,
+    */
+   private static int getMethodId(Method m)
+   {
+      Class clz = m.getDeclaringClass();
+      
+      if (clz == TransactionFactory.class)
+      {
+         String name = m.getName();
+         
+         if (name.equals("create"))
+            return ITransactionFactory.M_CREATE;
+         else /* name.equals("recreate") */
+            return ITransactionFactory.M_RECREATE;
+      }
+      else if (clz == Coordinator.class)
+      {
+         String name = m.getName();
+         
+         if (name.equals("getStatus"))
+            return ICoordinator.M_GET_STATUS;
+         else if (name.equals("isSameTransaction"))
+            return ICoordinator.M_IS_SAME_TRANSACTION;
+         else if (name.equals("hashTransaction"))
+            return ICoordinator.M_HASH_TRANSACTION;
+         else if (name.equals("registerResource"))
+            return ICoordinator.M_REGISTER_RESOURCE;
+         else if (name.equals("registerSynchronization"))
+            return ICoordinator.M_REGISTER_SYNCHRONIZATION;
+         else if (name.equals("rollbackOnly"))
+            return ICoordinator.M_ROLLBACK_ONLY;
+         else if (name.equals("getTransactionContext"))
+            return ICoordinator.M_GET_TRANSACTION_CONTEXT;
+         else /* name.equals("getTransactionId") */
+            return ICoordinator.M_GET_TRANSACTION_ID;
+      }
+      else if (clz == Terminator.class)
+      {
+         String name = m.getName();
+         
+         if (name.equals("commit"))
+            return ITerminator.M_COMMIT;
+         else /* name.equals("rollback") */
+            return ITerminator.M_ROLLBACK;
+         
+      }
+      else if (clz == Resource.class)
+      {
+         String name = m.getName();
+         
+         if (name.equals("prepare"))
+            return IResource.M_PREPARE;
+         else if (name.equals("rollback"))
+            return IResource.M_ROLLBACK;
+         else if (name.equals("commit"))
+            return IResource.M_COMMIT;
+         else if (name.equals("commitOnePhase"))
+            return IResource.M_COMMIT_ONE_PHASE;
+         else /* name.equals("forget") */
+            return IResource.M_FORGET;
+      }
+      else if (clz == RecoveryCoordinator.class)
+      {
+         return IRecoveryCoordinator.M_REPLAY_COMPLETION;
+      }
+      else if (clz == Synchronization.class)
+      {
+         String name = m.getName();
+         
+         if (name.equals("beforeCompletion"))
+            return ISynchronization.M_BEFORE_COMPLETION;
+         else /* name.equals("afterCompletion") */
+            return ISynchronization.M_AFTER_COMPLETION;
+      }
+      else
+      {
+         throw new RuntimeException("Method " + m + " does not belong to" + 
+                                    " a transaction service interface");
+      }
+   }
+   
+   // Attributes ----------------------------------------------------
+
+   /** Specifies the logical target of this <code>Invocation</code>. */
+   private long targetId;
+   
+   /** Specifies the method to be invoked. */
+   private int methodId;
+   
+   /** The arguments for the method invocation. */
+   private Object[] args;
+   
+   // Constructors --------------------------------------------------
+
+   /** Builds a new <code>Invocation</code> instance. */
+   public Invocation(long targetId,
+                     Method method,
+                     Object[] args)
+   {
+      this.targetId = targetId;
+      this.methodId = getMethodId(method);
+      this.args = args;
+   }
+   
+   /** Uses the given servant to perform this <code>Invocation</code>. */
+   public Object perform(InvokerLocator locator, Object servant) 
+         throws Throwable
+   {
+      return invokerArray[methodId].invoke(servant, targetId, args);      
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/RemoteProxy.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/RemoteProxy.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/RemoteProxy.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,95 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting;
+
+import java.lang.reflect.Proxy;
+
+import org.jboss.remoting.InvokerLocator;
+
+/**
+ * Utility class for creating remote proxies, converting them to strings and
+ * converting strings to remote proxies.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public class RemoteProxy
+{
+   /**
+    * Create a remote proxy given a DTM interface, a target object id and
+    * an array of <code>InvokerLocator</code>s.
+    *  
+    * @param interf   the DTM interface to be implemented by the proxy
+    * @param oid      the id of the remote object proxified 
+    * @param locators the array of invoker locators
+    * @return         a newly created proxy
+    */
+   public static Object create(Class interf, 
+                               long oid, 
+                               InvokerLocator[] locators) 
+   {
+      try
+      {
+         ClientInvocationHandler handler =
+            new ClientInvocationHandler(interf, oid, locators);
+         return Proxy.newProxyInstance(interf.getClassLoader(),
+                                       new Class[] { interf },
+                                       handler);
+      }
+      catch (Exception e)
+      {
+         e.printStackTrace();
+         return null;
+      }
+   }
+   
+   /**
+    * Converts a DTM proxy to String
+    * 
+    * @param p  a DTM proxy
+    * @return   the string representation of the proxy
+    */
+   public static String toString(Proxy p)
+   {
+      ClientInvocationHandler handler = 
+            (ClientInvocationHandler) Proxy.getInvocationHandler(p);
+      return handler.toString();
+   }
+   
+   /**
+    * Converts a stringfied DTM proxy back into a proxy instance. 
+    *  
+    * @param s  the string representation of a DTM proxy
+    * @return   a DTM proxy
+    * @throws Exception
+    */
+   public static Object fromString(String s)
+         throws Exception
+   {
+      ClientInvocationHandler handler = ClientInvocationHandler.fromString(s);
+      Class interf = handler.getClientInterface(); 
+      return Proxy.newProxyInstance(interf.getClassLoader(),
+                                    new Class[] { interf },
+                                    handler);
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransaction.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransaction.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransaction.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,371 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.client;
+
+import java.io.Serializable;
+import java.rmi.AccessException;
+import java.rmi.NoSuchObjectException;
+import java.rmi.RemoteException;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.naming.Reference;
+import javax.naming.Referenceable;
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionRolledbackException;
+import javax.transaction.UserTransaction;
+
+import org.jboss.tm.TransactionPropagationContextFactory;
+import org.jboss.tm.remoting.interfaces.HeuristicHazardException;
+import org.jboss.tm.remoting.interfaces.Status;
+import org.jboss.tm.remoting.interfaces.TransactionFactory;
+import org.jboss.tm.remoting.interfaces.TransactionInactiveException;
+import org.jboss.tm.remoting.interfaces.TxPropagationContext;
+
+
+/**
+ * The client-side UserTransaction implementation for JBoss remoting clients.
+ * This will delegate all UserTransaction calls to the DTM in the server.
+ *
+ * <em>Warning:</em> This is only for stand-alone JBoss remoting clients that 
+ * do not have their own transaction service. No local work is done in
+ * the context of transactions started here, only work done in beans
+ * at the server. 
+ *
+ * @author  <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class ClientUserTransaction
+      implements UserTransaction,
+                 TransactionPropagationContextFactory,
+                 Referenceable,
+                 Serializable
+{
+   // Static --------------------------------------------------------
+
+   static final long serialVersionUID = -3704980350844202097L;
+
+   /** Our singleton instance. */
+   private static ClientUserTransaction singleton = null;
+
+   /** The JNDI name to which the server's transaction factory proxy is bound */
+   public static final String TX_FACTORY_JNDI_NAME = "DTMTransactionFactory";
+   
+   /** Remote proxy to the server's transaction factory. */
+   private static TransactionFactory txFactory;
+
+   /** Transaction information associated with the current thread. */
+   private static ThreadLocal threadLocalData = new ThreadLocal() {
+         protected synchronized Object initialValue() 
+         {
+            return new TransactionInfo(); // see nested class below
+         }
+      };
+ 
+   // Nested class  -------------------------------------------------
+
+   /**
+    * The <code>TransactionInfo</code> class holds transaction information 
+    * associated with the current thread. The <code>threadLocalData</code> 
+    * field contains an instance of this class. The field timeout applies
+    * to new transactions started by the current thread; its value is not 
+    * necessarily equal to the time out of the currrent transaction. The 
+    * <code>TxPropagationContext</code> field refers to the currrent 
+    * transaction.
+    */
+   private static class TransactionInfo
+   {
+      int timeout = 0;   // for new transactions started by the current thread
+      TxPropagationContext tpc; // null if no current transaction
+   }
+
+   // Static accessors to thread-local data -------------------------
+
+   private static void setThreadLocalTimeout(int timeout)
+   {
+      ((TransactionInfo)threadLocalData.get()).timeout = timeout;
+   }
+
+   private static int getThreadLocalTimeout()
+   {
+      return ((TransactionInfo)threadLocalData.get()).timeout;
+   }
+   
+   private static void setThreadLocalTPC(TxPropagationContext tpc)
+   {
+      ((TransactionInfo)threadLocalData.get()).tpc = tpc;
+   }
+
+   private static TxPropagationContext getThreadLocalTPC()
+      throws IllegalStateException
+   {
+      return ((TransactionInfo)threadLocalData.get()).tpc;
+   }
+   
+   // Other auxiliary (and static) methods  -------------------------
+
+   /**
+    *  Returns a CORBA reference to the TransactionFactory implemented by 
+    *  the JBoss server.
+    */
+   private static TransactionFactory getTxFactory()
+   {
+      if (txFactory == null)
+      {
+         try 
+         {
+            Context ctx = new InitialContext();
+            txFactory = (TransactionFactory) ctx.lookup(TX_FACTORY_JNDI_NAME);
+         }
+         catch (Exception e)
+         {
+            throw new RuntimeException("Could not get transaction factory: ", e); 
+         }
+      }
+      return txFactory;
+   }
+
+   /**
+    *  Converts transaction status from org.omg.CosTransactions format 
+    *  to javax.transaction format.
+    */
+   private static int jbossToJavax(Status status)
+   {
+      return status.toInteger();
+   }
+
+   // Constructors --------------------------------------------------
+
+   /**
+    *  Create a new instance.
+    */
+   private ClientUserTransaction()
+   {
+   }
+
+   // Public --------------------------------------------------------
+
+   /**
+    *  Returns a reference to the singleton instance.
+    */
+   public static ClientUserTransaction getSingleton()
+   {
+      if (singleton == null)
+         singleton = new ClientUserTransaction();
+      return singleton;
+   }
+
+   //
+   // Implementation of interface UserTransaction
+   //
+
+   public void begin()
+      throws NotSupportedException, SystemException
+   {
+      if (getThreadLocalTPC() != null)
+         throw new NotSupportedException();
+      try
+      {
+         TxPropagationContext tpc = 
+            getTxFactory().create(getThreadLocalTimeout());
+         setThreadLocalTPC(tpc);
+      }
+      catch (RemoteException e)
+      {
+         SystemException e2 = new SystemException("Unable to begin transaction");
+         e2.initCause(e);
+         throw e2;
+      }
+   }
+
+   public void commit()
+      throws RollbackException,
+             HeuristicMixedException,
+             HeuristicRollbackException,
+             SecurityException,
+             IllegalStateException,
+             SystemException
+   {
+      try
+      {
+         TxPropagationContext tpc = getThreadLocalTPC();
+
+         if (tpc == null)
+            throw new IllegalStateException();
+
+         tpc.terminator.commit(true /* reportHeuristics */);
+      }
+      catch (TransactionRolledbackException e)
+      {
+         RollbackException e2 = new RollbackException("Transaction rolled back");
+         e2.initCause(e);
+         throw e2;
+      }
+      catch (HeuristicHazardException e)
+      {
+         HeuristicRollbackException e2 = new HeuristicRollbackException("Heuristic hazard");
+         e2.initCause(e);
+         throw e2;
+      }
+      catch (AccessException e)
+      {
+         SecurityException e2 = new SecurityException("Access denied");
+         e2.initCause(e);
+         throw e2;
+      }
+      catch (RemoteException e)
+      {
+         SystemException e2 = new SystemException("Error during commit");
+         e2.initCause(e);
+         throw e2;
+      }
+      finally
+      {
+         setThreadLocalTPC(null);
+      }
+   }
+
+   public void rollback()
+      throws SecurityException,
+             IllegalStateException,
+             SystemException
+   {
+      try
+      {
+         TxPropagationContext tpc = getThreadLocalTPC();
+         
+         if (tpc == null)
+            throw new IllegalStateException();
+         
+         tpc.terminator.rollback();
+      }
+      catch (AccessException e)
+      {
+         SecurityException e2 = new SecurityException("Access denied");
+         e2.initCause(e);
+         throw e2;
+      }
+      catch (RemoteException e)
+      {
+         SystemException e2 = new SystemException("Error during rollback");
+         e2.initCause(e);
+         throw e2;
+      }
+      finally
+      {
+         setThreadLocalTPC(null);
+      }
+   }
+
+   public void setRollbackOnly()
+      throws IllegalStateException,
+             SystemException
+   {
+      try
+      {
+         TxPropagationContext tpc = getThreadLocalTPC();
+         
+         if (tpc == null)
+            throw new IllegalStateException();
+
+         tpc.coordinator.rollbackOnly();
+      }
+      catch (TransactionInactiveException e)
+      {
+         IllegalStateException e2 = new IllegalStateException("Transaction is not active");
+         e2.initCause(e);
+         throw e2;
+      }
+      catch (RemoteException e)
+      {
+         SystemException e2 = new SystemException("Error during rollback");
+         e2.initCause(e);
+         throw e2;
+      }
+   }
+
+   public int getStatus()
+      throws SystemException
+   {
+      try
+      {
+         TxPropagationContext tpc = getThreadLocalTPC();
+         
+         if (tpc == null)
+            return javax.transaction.Status.STATUS_NO_TRANSACTION;
+         else 
+            return jbossToJavax(tpc.coordinator.getStatus());
+      }
+      catch (NoSuchObjectException e)
+      {
+         return javax.transaction.Status.STATUS_NO_TRANSACTION;
+      }
+      catch (RemoteException e)
+      {
+         SystemException e2 = new SystemException("Error getting status");
+         e2.initCause(e);
+         throw e2;
+      }
+   }
+
+   public void setTransactionTimeout(int seconds)
+      throws SystemException
+   {
+      setThreadLocalTimeout(seconds);
+   }
+   
+   //
+   // implements interface TransactionPropagationContextFactory
+   //
+
+   public Object getTransactionPropagationContext()
+   {
+      return getThreadLocalTPC();
+   }
+
+   public Object getTransactionPropagationContext(Transaction tx)
+   {
+      // No need to implement in a stand-alone client.
+      throw new InternalError("Should not have been used.");
+   }
+
+   //
+   // Implementation of interface Referenceable
+   //
+
+   public Reference getReference()
+      throws NamingException
+   {
+      Reference ref = new Reference(
+            "org.jboss.tm.remoting.client.ClientUserTransaction",
+            "org.jboss.tm.remoting.client.ClientUserTransactionObjectFactory",
+            null);
+      return ref;
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransactionObjectFactory.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransactionObjectFactory.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/client/ClientUserTransactionObjectFactory.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,92 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.client;
+
+import org.jboss.tm.TransactionPropagationContextUtil;
+import org.jboss.tm.usertx.client.ServerVMClientUserTransaction;
+
+import java.util.Hashtable;
+
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.Reference;
+import javax.naming.Name;
+import javax.naming.NamingException;
+import javax.naming.spi.ObjectFactory;
+
+import javax.transaction.UserTransaction;
+
+/**
+ *  This is an object factory for producing client <code>UserTransaction</code>
+ *  instances.
+ * 
+ *  @author <a href="mailto:osh at sparre.dk">Ole Husgaard</a>
+ *  @version $Revision: 37459 $
+ */
+public class ClientUserTransactionObjectFactory
+   implements ObjectFactory
+{
+   /**
+    *  The <code>UserTransaction</code> this factory will return.
+    *  This is evaluated lazily in {@link #getUserTransaction()}.
+    */
+   static private UserTransaction userTransaction = null;
+
+   /**
+    *  Get the <code>UserTransaction</code> this factory will return.
+    *  This may return a cached value from a previous call.
+    */
+   static private UserTransaction getUserTransaction()
+   {
+      if (userTransaction == null) {
+         // See if we have a local TM
+         try {
+            new InitialContext().lookup("java:/TransactionManager");
+
+            // We execute in the server.
+            userTransaction = ServerVMClientUserTransaction.getSingleton();
+         } catch (NamingException ex) {
+            // We execute in a stand-alone client.
+            ClientUserTransaction cut = ClientUserTransaction.getSingleton();
+
+            // Tell the proxy that this is the factory for
+            // transaction propagation contexts.
+            TransactionPropagationContextUtil.setTPCFactory(cut);
+            userTransaction = cut;
+         }
+      }
+      return userTransaction;
+   }
+
+   public Object getObjectInstance(Object obj, Name name,
+                                   Context nameCtx, Hashtable environment)
+      throws Exception
+   {
+      Reference ref = (Reference)obj;
+ 
+      if (!ref.getClassName().equals(ClientUserTransaction.class.getName()))
+         return null;
+
+      return getUserTransaction();
+   }
+}
+

Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Coordinator.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Coordinator.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Coordinator.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,67 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.interfaces;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+import org.jboss.tm.GlobalId;
+
+/**
+ * Interface used by the participants in a transaction.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public interface Coordinator extends Remote
+{
+
+   Status getStatus()
+         throws RemoteException;
+   
+   boolean isSameTransaction(Coordinator c)
+         throws RemoteException;
+
+   int hashTransaction()
+         throws RemoteException;
+
+   RecoveryCoordinator registerResource(Resource r)
+         throws RemoteException,
+                TransactionInactiveException;
+
+   void registerSynchronization(Synchronization sync)
+         throws RemoteException,
+                TransactionInactiveException,
+                SynchronizationUnavailableException;
+   
+   void rollbackOnly()
+         throws RemoteException,
+                TransactionInactiveException;
+   
+   TxPropagationContext getTransactionContext()
+         throws RemoteException,
+                TransactionInactiveException;
+
+   GlobalId getTransactionId()   
+         throws RemoteException;
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/HeuristicHazardException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/HeuristicHazardException.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/HeuristicHazardException.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,52 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.interfaces;
+
+/**
+ * Indicates that a heuristic decision may have been made, the disposition of
+ * all relevant updates is not known, and for those updates whose disposition
+ * is knwon, either all have been committed or all have been rolled back. (In
+ * other words, the <code>HeuristicMixed</code> exception takes priority over
+ * this exception.
+ *
+ * @author <a href="reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public class HeuristicHazardException extends Exception
+{
+   private static final long serialVersionUID = -3687274256195397687L;
+
+   public HeuristicHazardException()
+   {
+   }
+
+   public HeuristicHazardException(String msg)
+   {
+      super(msg);
+   }
+   
+   public HeuristicHazardException(Throwable cause)
+   {
+      super(cause);
+   }
+   
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/RecoveryCoordinator.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/RecoveryCoordinator.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/RecoveryCoordinator.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,41 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.interfaces;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+
+/**
+ * Interface that allows a recoverable object to drive the recovery process.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public interface RecoveryCoordinator extends Remote 
+{
+
+   Status replayCompletion(Resource r)
+         throws RemoteException,
+                TransactionNotPreparedException;
+}
+

Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Resource.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Resource.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Resource.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,68 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.interfaces;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+import javax.transaction.HeuristicCommitException;
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+
+
+/**
+ * Interface that provides operations invoked by the transaction service
+ * on each resource.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public interface Resource extends Remote 
+{
+   
+   Vote prepare()
+         throws RemoteException,
+                TransactionAlreadyPreparedException,
+                HeuristicMixedException,
+                HeuristicHazardException;
+
+   void rollback()
+         throws RemoteException,
+                HeuristicCommitException,
+                HeuristicMixedException,
+                HeuristicHazardException;
+
+   void commit()
+         throws RemoteException,
+                TransactionNotPreparedException,
+                HeuristicRollbackException,
+                HeuristicMixedException,
+                HeuristicHazardException;
+   
+   void commitOnePhase()
+         throws RemoteException,
+                HeuristicHazardException;
+   
+   void forget()
+         throws RemoteException;
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Status.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Status.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Status.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,105 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.interfaces;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+/**
+ * Type safe enumeration for the status of a transaction.
+ *
+ * @author Scott.Stark at jboss.org
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public class Status implements Serializable
+{
+   static final long serialVersionUID = -498123265225476852L;
+
+   /** 
+    * The max ordinal value in use for the Status enums. When you add a
+    * new key enum value you must assign it an ordinal value of the current
+    * MAX_TYPE_ID+1 and update the MAX_TYPE_ID value.
+    */
+   private static final int MAX_TYPE_ID = 9;
+
+   /** The array of Status indexed by ordinal value of the key */
+   private static final Status[] values = new Status[MAX_TYPE_ID + 1];
+
+   // IMPORTANT: The ordinal values below are equal to the corresponding
+   // values in org.omg.CosTransactions.Status and in javax.transaction.Status.
+   // This allows efficient conversion between 
+   // org.jboss.tm.remoting.interfaces.Status instances
+   // and org.omg.CosTransactions.Status or javax.transaction.Status instances.
+   public static final Status ACTIVE = new Status("ACTIVE", 0);
+
+   public static final Status MARKED_ROLLBACK = new Status("MARKED_ROLLBACK", 1);
+
+   public static final Status PREPARED = new Status("PREPARED", 2);
+
+   public static final Status COMMITTED = new Status("COMMITTED", 3);
+
+   public static final Status ROLLEDBACK = new Status("ROLLEDBACK", 4);
+
+   public static final Status UNKNOWN = new Status("UNKNOWN", 5);
+
+   public static final Status NO_TRANSACTION = new Status("NO_TRANSACTION", 6);
+
+   public static final Status PREPARING = new Status("PREPARING", 7);
+
+   public static final Status COMMITTING = new Status("COMMITTING", 8);
+
+   public static final Status ROLLINGBACK = new Status("ROLLINGBACK", 9);
+
+   private final transient String name;
+
+   // this is the only value serialized
+   private final int ordinal;
+
+   private Status(String name, int ordinal)
+   {
+      this.name = name;
+      this.ordinal = ordinal;
+      values[ordinal] = this;
+   }
+
+   public String toString()
+   {
+      return name;
+   }
+
+   public int toInteger()
+   {
+      return ordinal;
+   }
+
+   public static Status fromInteger(int i)
+   {
+      return values[i];
+   }
+
+   Object readResolve() throws ObjectStreamException
+   {
+      return values[ordinal];
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Synchronization.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Synchronization.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Synchronization.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,43 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.interfaces;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+/**
+ * Interface implemented by objects that want to be notified before the start
+ * of the two-phase commit protocol, and after its competion.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public interface Synchronization extends Remote
+{
+
+   void beforeCompletion()
+         throws RemoteException;
+   
+   void afterCompletion(Status s)
+         throws RemoteException;
+   
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/SynchronizationUnavailableException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/SynchronizationUnavailableException.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/SynchronizationUnavailableException.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,35 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.interfaces;
+
+/**
+ * Exception thrown by <code>Coordinator.registerSynchronization</code>
+ * to indicate that the target coordinator does not support synchronization
+ * callbacks.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public class SynchronizationUnavailableException extends Exception
+{
+   static final long serialVersionUID = -4736175108426365516L;
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Terminator.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Terminator.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Terminator.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,47 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.interfaces;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+import javax.transaction.HeuristicMixedException;
+
+
+/**
+ * Interface that provides operations to commit or rollback a transaction.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public interface Terminator extends Remote
+{
+   
+   void commit(boolean reportHeuristics) 
+         throws RemoteException, 
+                HeuristicMixedException, 
+                HeuristicHazardException;
+   
+   void rollback()
+         throws RemoteException;
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionAlreadyPreparedException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionAlreadyPreparedException.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionAlreadyPreparedException.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,49 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.interfaces;
+
+/**
+ * Exception thrown when <code>prepare</code> is called on a resource that is
+ * already prepared.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public class TransactionAlreadyPreparedException extends Exception
+{
+   static final long serialVersionUID = 7862561036798964308L;
+   
+   public TransactionAlreadyPreparedException()
+   {
+   }
+   
+   public TransactionAlreadyPreparedException(String msg)
+   {
+      super(msg);
+   }
+   
+   public TransactionAlreadyPreparedException(Throwable cause)
+   {
+      super(cause);
+   }
+   
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionFactory.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionFactory.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionFactory.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,43 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.interfaces;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+
+
+/**
+ * Interface that allows the transaction originator to begin a transaction.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public interface TransactionFactory extends Remote
+{
+   
+   TxPropagationContext create(int timeout)
+         throws RemoteException;
+   
+   TxPropagationContext recreate(TxPropagationContext tpc)
+         throws RemoteException;
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionInactiveException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionInactiveException.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionInactiveException.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,49 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.interfaces;
+
+/**
+ * Indicates that a transaction expected to be in the active state 
+ * has already been prepared for commit or marked for rollback.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public class TransactionInactiveException extends Exception 
+{
+   static final long serialVersionUID = -846122012000355051L;
+
+   public TransactionInactiveException()
+   {
+   }
+
+   public TransactionInactiveException(String msg)
+   {
+      super(msg);
+   }
+
+   public TransactionInactiveException(Throwable cause)
+   {
+      super(cause);
+   }
+   
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionNotPreparedException.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionNotPreparedException.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TransactionNotPreparedException.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,49 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.interfaces;
+
+/**
+ * Indicates that a transaction expected to be in the prepared state 
+ * is actually not prepared.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public class TransactionNotPreparedException extends Exception 
+{
+   static final long serialVersionUID = -4131393859923384723L;
+  
+   public TransactionNotPreparedException()
+   {
+   }
+
+   public TransactionNotPreparedException(String msg)
+   {
+      super(msg);
+   }
+
+   public TransactionNotPreparedException(Throwable cause)
+   {
+      super(cause);
+   }
+   
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TxPropagationContext.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TxPropagationContext.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/TxPropagationContext.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,85 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.interfaces;
+
+import java.io.Serializable;
+
+
+/**
+ * The <code>TransactionFactory</code> operations return instances of this
+ * class, which defines the transaction context to be propagated along with
+ * outgoing invocations issued through the remoting framework. In the IIOP 
+ * case, however, the transaction context is not propagated as a 
+ * <code>TxPropagationContext</code> instance, but in the standard format
+ * defined by the CORBA Transaction Service specification. 
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public class TxPropagationContext implements Serializable
+{
+   static final long serialVersionUID = 88513087659796235L;
+
+   private static String thisClassName;
+   
+   static {
+      thisClassName = TxPropagationContext.class.getName();
+      thisClassName = 
+         thisClassName.substring(thisClassName.lastIndexOf('.') + 1);
+   }
+   
+   public int formatId;
+   public byte[] globalId;
+   public int timeout;
+   public Coordinator coordinator;
+   public Terminator terminator;
+   
+   public TxPropagationContext(int formatId,
+                                byte[] globalId,
+                                int timeout,                                
+                                Coordinator coordinator,
+                                Terminator terminator)
+                                
+   {
+      this.formatId = formatId;
+      this.globalId = globalId;
+      this.timeout = timeout;
+      this.coordinator = coordinator;
+      this.terminator = terminator;
+   }
+   
+   public TxPropagationContext(int formatId,
+                                byte[] globalId,
+                                int timeout,
+                                Coordinator coordinator)
+   {
+      this(formatId, globalId, timeout, coordinator, null);
+   }
+   
+   public String toString() 
+   {
+      return thisClassName + "[formatId=" + formatId
+            + ", globalId=" + new String(globalId).trim()
+            + ", timeout=" + timeout + "]";
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Vote.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Vote.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/interfaces/Vote.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,74 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.interfaces;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+/**
+ * Type safe enumeration for <code>Resource</code> votes.
+ *
+ * @author Scott.Stark at jboss.org
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public class Vote implements Serializable
+{
+   static final long serialVersionUID = -6997498750766547785L;
+
+   /** 
+    * The max ordinal value in use for the Vote enums. When you add a
+    * new key enum value you must assign it an ordinal value of the current
+    * MAX_TYPE_ID+1 and update the MAX_TYPE_ID value.
+    */
+   private static final int MAX_TYPE_ID = 2;
+
+   /** The array of Vote indexed by ordinal value of the key */
+   private static final Vote[] values = new Vote[MAX_TYPE_ID + 1];
+   
+   public static final Vote COMMIT = new Vote("COMMIT", 0);
+   public static final Vote ROLLBACK = new Vote("ROLLBACK", 1);
+   public static final Vote READONLY = new Vote("READONLY", 2);
+   
+   private final transient String name;
+
+   // this is the only value serialized
+   private final int ordinal;
+
+   private Vote(String name, int ordinal)
+   {
+      this.name = name;
+      this.ordinal = ordinal;
+      values[ordinal] = this;
+   }
+
+   public String toString()
+   {
+      return name;
+   }
+   
+   Object readResolve() throws ObjectStreamException
+   {
+      return values[ordinal];
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMInvocationHandler.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMInvocationHandler.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMInvocationHandler.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,93 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.server;
+
+import javax.management.MBeanServer;
+
+import org.jboss.remoting.InvocationRequest;
+import org.jboss.remoting.ServerInvocationHandler;
+import org.jboss.remoting.ServerInvoker;
+import org.jboss.remoting.callback.InvokerCallbackHandler;
+import org.jboss.tm.remoting.Invocation;
+
+/**
+ * <code>ServerInvocationHandler</code> for the DTM subsystem.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $
+ */
+public class DTMInvocationHandler implements ServerInvocationHandler
+{
+   private DTMServant dtmServant;
+   private ServerInvoker invoker;
+
+   DTMInvocationHandler(DTMServant dtmServant)
+   {
+      this.dtmServant = dtmServant;
+   }
+
+   /**
+    * Empty method - this invocation handler does not keep a reference to the <code>MBeanServer</code>.
+    * @see org.jboss.remoting.ServerInvocationHandler#setMBeanServer(javax.management.MBeanServer)
+    */
+   public void setMBeanServer(MBeanServer server)
+   {
+   }
+
+   /**
+    * Sets this invovation handler's <code>ServerInvoker</code>.
+    * @see org.jboss.remoting.ServerInvocationHandler#setInvoker(org.jboss.remoting.ServerInvoker)
+    */
+   public void setInvoker(ServerInvoker invoker)
+   {
+      this.invoker = invoker;
+   }
+
+   /**
+    * Performs the invocation specified by a given <code>InvocationRequest</code>,
+    * @see org.jboss.remoting.ServerInvocationHandler#invoke(org.jboss.remoting.InvocationRequest)
+    */
+   public Object invoke(InvocationRequest invocationRequest)
+         throws Throwable
+   {
+      Invocation invocation = (Invocation)invocationRequest.getParameter();
+
+      return invocation.perform(invoker.getLocator(), dtmServant);
+   }
+
+   /**
+    * Empty method - listener registration is not supported.
+    * @see org.jboss.remoting.ServerInvocationHandler#addListener(org.jboss.remoting.callback.InvokerCallbackHandler)
+    */
+   public void addListener(InvokerCallbackHandler callbackHandler)
+   {
+   }
+
+   /**
+    * Empty method - listener registration is not supported.
+    * @see org.jboss.remoting.ServerInvocationHandler#removeListener(org.jboss.remoting.callback.InvokerCallbackHandler)
+    */
+   public void removeListener(InvokerCallbackHandler callbackHandler)
+   {
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMServant.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMServant.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/server/DTMServant.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,967 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.server;
+
+import java.lang.reflect.Proxy;
+import java.rmi.AccessException;
+import java.rmi.NoSuchObjectException;
+import java.rmi.RemoteException;
+import java.rmi.UnexpectedException;
+
+import javax.transaction.HeuristicCommitException;
+import javax.transaction.HeuristicMixedException;
+import javax.transaction.HeuristicRollbackException;
+import javax.transaction.NotSupportedException;
+import javax.transaction.RollbackException;
+import javax.transaction.SystemException;
+import javax.transaction.Transaction;
+import javax.transaction.TransactionManager;
+import javax.transaction.TransactionRolledbackException;
+import javax.transaction.xa.XAResource;
+
+import org.jboss.logging.Logger;
+import org.jboss.tm.CoordinatorFactory;
+import org.jboss.tm.GlobalId;
+import org.jboss.tm.LocalId;
+import org.jboss.tm.ResourceFactory;
+import org.jboss.tm.StringRemoteRefConverter;
+import org.jboss.tm.TransactionImpl;
+import org.jboss.tm.TMUtil;
+import org.jboss.tm.TxManager;
+import org.jboss.tm.XidImpl;
+import org.jboss.tm.remoting.RemoteProxy;
+import org.jboss.tm.remoting.Invocation.ICoordinator;
+import org.jboss.tm.remoting.Invocation.IRecoveryCoordinator;
+import org.jboss.tm.remoting.Invocation.IResource;
+import org.jboss.tm.remoting.Invocation.ISynchronization;
+import org.jboss.tm.remoting.Invocation.ITerminator;
+import org.jboss.tm.remoting.Invocation.ITransactionFactory;
+import org.jboss.tm.remoting.interfaces.Coordinator;
+import org.jboss.tm.remoting.interfaces.HeuristicHazardException;
+import org.jboss.tm.remoting.interfaces.RecoveryCoordinator;
+import org.jboss.tm.remoting.interfaces.Resource;
+import org.jboss.tm.remoting.interfaces.Status;
+import org.jboss.tm.remoting.interfaces.Synchronization;
+import org.jboss.tm.remoting.interfaces.Terminator;
+import org.jboss.tm.remoting.interfaces.TxPropagationContext;
+import org.jboss.tm.remoting.interfaces.TransactionInactiveException;
+import org.jboss.tm.remoting.interfaces.TransactionNotPreparedException;
+import org.jboss.tm.remoting.interfaces.Vote;
+
+/**
+ * Remoting servant for the following Distributed Transaction Manager
+ * interfaces: <code>TransactionFactory</code>, <code>Coordinator</code>,
+ * <code>Terminator</code>, <code>RecoveryCoordinator</code>, 
+ * <code>Synchronization</code>.
+ * 
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public class DTMServant implements ITransactionFactory, 
+                                   ICoordinator,
+                                   ITerminator,
+                                   IResource,
+                                   IRecoveryCoordinator, 
+                                   ISynchronization,
+                                   CoordinatorFactory,
+                                   ResourceFactory,
+                                   StringRemoteRefConverter
+{
+
+   // Constants  ----------------------------------------------------
+
+   private static final Logger log = 
+      Logger.getLogger(DTMServant.class);
+
+   // Private fields ------------------------------------------------
+
+   private DistributedTransactionManager dtm;
+
+   // Constructor ---------------------------------------------------
+
+   public DTMServant(DistributedTransactionManager dtm)
+   {
+      this.dtm = dtm;
+   }
+   
+   // ITransactionFactory methods -----------------------------------
+   
+   public TxPropagationContext create(long targetId, int timeout)
+         throws RemoteException
+   {
+      log.trace("TransactionFactory.create");
+      try
+      {
+         TransactionManager tm = TMUtil.getTransactionManager();
+
+         // Set timeout value
+         if (timeout != 0)
+            tm.setTransactionTimeout(timeout);
+
+         // Start tx 
+         tm.begin();
+
+         // Suspend thread association 
+         // and get the xid and the local id of the transaction
+         TransactionImpl tx = (TransactionImpl)tm.suspend();
+         XidImpl xid = tx.getXid();
+         long localId = xid.getLocalIdValue();
+
+         // Get the TxPropagationContext of the transaction
+         TxPropagationContext tpc = tx.getPropagationContext();
+         
+         // Set the terminator in the TPC and return the TPC
+         tpc.terminator = 
+            (Terminator) RemoteProxy.create(Terminator.class,
+                  localId,
+                  dtm.getLocators());
+         
+         return tpc;
+      }
+      catch (SystemException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new UnexpectedException("Unexpected " + e, e);
+      } 
+      catch (NotSupportedException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new UnexpectedException("Unexpected " + e, e);
+      }
+   }
+
+   public TxPropagationContext recreate(long targetId,
+                                        TxPropagationContext tpc)
+         throws RemoteException
+   {
+      log.trace("TransactionFactory.recreate");
+      
+      if (tpc == null)
+         throw new IllegalArgumentException(
+               "recreate: TxPropagationContext parameter cannot be null");
+      
+      TxManager tm = (TxManager) TMUtil.getTransactionManager();
+      TransactionImpl tx = tm.importExternalTransaction(tpc.formatId, 
+                                                        tpc.globalId,
+                                                        tpc.coordinator,
+                                                        tpc.timeout * 1000);
+            
+      // Create remote proxy to the local Coordinator
+      Coordinator subCoordinator = 
+         (Coordinator) RemoteProxy.create(Coordinator.class,
+                                          tx.getLocalIdValue(),
+                                          dtm.getLocators());
+
+      // Return TPC with the interposed subCoordinator and a null Terminator
+      return new TxPropagationContext(tpc.formatId,
+                                      tpc.globalId, 
+                                      tpc.timeout, 
+                                      subCoordinator, 
+                                      null);
+   }
+
+   // ICoordinator methods ------------------------------------------
+   
+   public Status getStatus(long targetId)
+         throws RemoteException
+   {
+      if (log.isTraceEnabled())
+         log.trace("Coordinator.getStatus, targetId=" + 
+                   Long.toHexString(targetId));
+      
+      LocalId localId = new LocalId(targetId);
+      Transaction tx = TMUtil.getTransaction(localId);
+
+      if (tx == null)
+      {
+         log.trace("RemoteException in getStatus: transaction not found");
+         // Not sure if here it would be better to return StatusNoTransaction 
+         // instead of throwing NoSuchObjectException... (Francisco)
+         throw new NoSuchObjectException("No transaction.");
+         // return Status.NO_TRANSACTION; // ?
+      }
+      
+      int status;
+
+      try
+      {
+         status = tx.getStatus();
+      }
+      catch (SystemException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new UnexpectedException("Unexpected " + e, e);
+      }
+      return javaxToJBoss(status);
+   }
+
+   public boolean isSameTransaction(long targetId, Coordinator c)
+         throws RemoteException
+   {
+      if (log.isTraceEnabled())
+         log.trace("Coordinator.isSameTransaction, targetId=" + 
+                   Long.toHexString(targetId));
+      return getTransactionId(targetId).equals(c.getTransactionId());
+   }
+
+   public int hashTransaction(long targetId)
+         throws RemoteException
+   {
+      if (log.isTraceEnabled())
+         log.trace("Coordinator.hashTransaction, targetId=" +
+                   Long.toHexString(targetId));
+      return getTransactionId(targetId).hashCode();
+   }
+
+   public RecoveryCoordinator registerResource(long targetId, Resource r)
+         throws RemoteException,
+                TransactionInactiveException
+   {
+      if (log.isTraceEnabled())
+         log.trace("Coordinator.registerResource, targetId=" + 
+                   Long.toHexString(targetId));
+      
+      LocalId localId = new LocalId(targetId);
+      TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+
+      if (tx == null)
+      {
+         log.trace("RemoteException in registerResource: " +
+                   "transaction not found");
+         throw new NoSuchObjectException("No transaction.");
+      }
+   
+      try
+      {
+         tx.enlistRemoteResource(r);
+      }
+      catch (RollbackException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Exception: ", e);
+         TransactionRolledbackException ex = 
+            new TransactionRolledbackException(e.toString());
+         ex.detail = e;
+         throw ex;
+      }
+      catch (IllegalStateException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Exception: ", e);
+         throw new TransactionInactiveException(e);
+      }
+
+      // Create remote proxies to the RecoveryCoordinator
+      RecoveryCoordinator rc = 
+         (RecoveryCoordinator) RemoteProxy.create(RecoveryCoordinator.class,
+                                                  targetId,
+                                                  dtm.getLocators());
+      return rc;
+   }
+
+   public void registerSynchronization(long targetId, 
+                                       final Synchronization sync)
+      throws RemoteException,
+             TransactionInactiveException
+   {
+      if (log.isTraceEnabled())
+         log.trace("Coordinator.registerSynchronization, targetId=" + 
+                   Long.toHexString(targetId));
+      
+      LocalId localId = new LocalId(targetId);
+      Transaction tx = TMUtil.getTransaction(localId);
+      
+      if (tx == null)
+      {
+         log.trace("RemoteException in registerSynchronization: " +
+                   "transaction not found");
+         throw new NoSuchObjectException("No transaction.");
+      }
+      
+      try 
+      {
+         tx.registerSynchronization(
+            new javax.transaction.Synchronization()
+            {
+               public void beforeCompletion() 
+               {
+                  try
+                  {
+                     sync.beforeCompletion();
+                  }
+                  catch (RemoteException e)
+                  {
+                     if (log.isTraceEnabled())
+                        log.trace("RemoteException in beforeCompletion: " + e);
+                  }
+               }
+               public void afterCompletion(int status)
+               {
+                  try
+                  {
+                     sync.afterCompletion(javaxToJBoss(status));
+                  }
+                  catch (RemoteException e)
+                  {
+                     if (log.isTraceEnabled())
+                        log.trace("RemoteException in afterCompletion: " + e);
+                  }
+               }
+            });
+      }
+      catch (RollbackException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Exception: ", e);
+         TransactionRolledbackException ex = 
+            new TransactionRolledbackException(e.toString());
+         ex.detail = e;
+         throw ex;         
+      }
+      catch (IllegalStateException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new TransactionInactiveException(e);
+      }
+      catch (SystemException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new UnexpectedException("Unexpected " + e, e);
+      }
+   }
+
+   public void rollbackOnly(long targetId) 
+         throws RemoteException, 
+                TransactionInactiveException
+   {
+      if (log.isTraceEnabled())
+         log.trace("Coordinator.rollbackOnly, targetId=" + 
+                   Long.toHexString(targetId));
+ 
+      LocalId localId = new LocalId(targetId);
+      Transaction tx = TMUtil.getTransaction(localId);
+
+      if (tx == null)
+      {
+         log.trace("RemoteException in rollbackOnly: transaction not found");
+         throw new NoSuchObjectException("No transaction.");
+      }
+      
+      try 
+      {
+         tx.setRollbackOnly();
+      }
+      catch (IllegalStateException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new TransactionInactiveException(e);
+      }
+      catch (SystemException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new UnexpectedException("Unexpected " + e, e);
+      }
+   }
+
+   public TxPropagationContext getTransactionContext(long targetId)
+         throws RemoteException,
+                TransactionInactiveException
+   {
+      if (log.isTraceEnabled())
+         log.trace("Coordinator.getTransactionContext, targetId=" + 
+                   Long.toHexString(targetId));
+      
+      LocalId localId = new LocalId(targetId);
+      TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+
+      if (tx == null)
+      {
+         log.trace("RemoteException in getTransactionContext: " + 
+                   "transaction not found");
+         throw new NoSuchObjectException("No transaction.");
+      }
+      
+      TxPropagationContext txPropagationContext = tx.getPropagationContext();
+      if (txPropagationContext != null)
+         return tx.getPropagationContext();
+      else
+         throw new TransactionInactiveException();
+   }
+
+   public GlobalId getTransactionId(long targetId)
+         throws RemoteException
+   {
+      if (log.isTraceEnabled())
+         log.trace("Coordinator.getTransactionId, targetId=" 
+                   + Long.toHexString(targetId));
+      
+      LocalId localId = new LocalId(targetId);
+      TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+
+      if (tx == null)
+      {
+         log.trace("RemoteException in getTransactionId: " + 
+                   "transaction not found");
+         throw new NoSuchObjectException("No transaction.");
+      }
+      
+      return tx.getGlobalId();
+   }
+
+   // ITerminator methods -------------------------------------------
+
+   public void commit(long targetId, boolean reportHeuristics)
+         throws RemoteException,
+                HeuristicMixedException, 
+                HeuristicHazardException
+   {
+      if (log.isTraceEnabled())
+         log.trace("Terminator.commit, targetId=" + 
+                   Long.toHexString(targetId));
+      
+      LocalId localId = new LocalId(targetId);
+      Transaction tx = TMUtil.getTransaction(localId);
+      
+      if (tx == null)
+      {
+         log.trace("RemoteException in commit: transaction not found");
+         throw new NoSuchObjectException("No transaction.");
+      }
+      
+      try 
+      {
+         tx.commit();
+      }
+      catch (RollbackException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Exception: ", e);
+         TransactionRolledbackException ex = 
+            new TransactionRolledbackException(e.toString());
+         ex.detail = e;
+         throw ex;
+      }
+      catch (HeuristicMixedException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Exception: ", e);
+         if (reportHeuristics) 
+         {
+            HeuristicMixedException ex = new HeuristicMixedException();
+            ex.initCause(e);
+            throw ex;
+         }
+      }
+      catch (HeuristicRollbackException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Exception: ", e);
+         TransactionRolledbackException ex = 
+            new TransactionRolledbackException(e.toString());
+         ex.detail = e;
+         throw ex;
+      }
+      catch (SecurityException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new AccessException(e.toString(), e);
+      }
+      catch (IllegalStateException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new RemoteException(e.toString(), e);
+      }
+      catch (SystemException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new UnexpectedException(e.toString(), e);
+      }
+   }
+
+   public void rollback(long targetId)
+         throws RemoteException
+   {
+      if (log.isTraceEnabled())
+         log.trace("Terminator.rollback, targetId=" + 
+                   Long.toHexString(targetId));
+      
+      LocalId localId = new LocalId(targetId);
+      Transaction tx = TMUtil.getTransaction(localId);
+      
+      if (tx == null)
+      {
+         log.trace("RemoteException in rollback: transaction not found");
+         throw new NoSuchObjectException("No transaction.");
+      }
+      
+      try 
+      {
+         tx.rollback();
+      }
+      catch (IllegalStateException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new RemoteException(e.toString(), e);
+      }
+      catch (SecurityException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new AccessException(e.toString(), e);
+      }
+      catch (SystemException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new UnexpectedException(e.toString(), e);
+      }
+   }
+
+   // IResource methods ------------------------------------------
+
+   public Vote prepare(long targetId) 
+         throws RemoteException,
+                HeuristicMixedException,
+                HeuristicHazardException
+   {
+      if (log.isTraceEnabled())
+         log.trace("Resource.prepare, targetId=" + Long.toHexString(targetId));
+      
+      LocalId localId = new LocalId(targetId);
+      TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+      
+      if (tx == null)
+      {
+         log.trace("RemoteException in prepare: transaction not found");
+         throw new NoSuchObjectException("No transaction.");
+      }
+      
+      TransactionManager tm = TMUtil.getTransactionManager();
+      try
+      {
+         tm.resume(tx);
+         int vote = tx.prepare(null);
+         
+         if (vote == XAResource.XA_OK)
+            return Vote.COMMIT;
+         else // (vote == XAResource.XA_RDONLY)
+            return Vote.READONLY;
+      }
+      catch (RollbackException e)
+      {
+         return Vote.ROLLBACK;
+      }
+      catch (javax.transaction.HeuristicMixedException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Exception: ", e);
+         HeuristicMixedException ex = new HeuristicMixedException();
+         ex.initCause(e);
+         throw ex;
+      }
+      catch (javax.transaction.HeuristicRollbackException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Exception: ", e);
+         HeuristicHazardException ex = new HeuristicHazardException();
+         ex.initCause(e);
+         throw ex;
+      }
+      catch (Exception e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new UnexpectedException(e.toString(), e);
+      }
+      finally
+      {
+         try
+         {
+            tm.suspend();
+         }
+         catch (SystemException e)
+         {
+            if (log.isTraceEnabled())
+               log.trace("Unexpected exception: ", e);
+            throw new UnexpectedException(e.toString(), e);
+         }
+      }
+   }
+
+   public void rollbackResource(long targetId) 
+         throws RemoteException,
+                HeuristicCommitException,
+                HeuristicMixedException, 
+                HeuristicHazardException
+   {
+      if (log.isTraceEnabled())
+         log.trace("Resource.rollback, targetId=" + 
+                   Long.toHexString(targetId));
+      
+      LocalId localId = new LocalId(targetId);
+      TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+      
+      if (tx == null)
+      {
+         log.trace("RemoteException in rollback: transaction not found");
+         throw new NoSuchObjectException("No transaction.");
+      }
+      
+      try
+      {
+         tx.rollbackBranch();
+      }
+      catch (IllegalStateException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new RemoteException(e.toString(), e);
+      }
+   }
+
+   public void commit(long targetId) 
+         throws RemoteException,
+                TransactionNotPreparedException,
+                HeuristicRollbackException, 
+                HeuristicMixedException,
+                HeuristicHazardException
+   {
+      if (log.isTraceEnabled())
+         log.trace("Resource.commit, targetId=" + Long.toHexString(targetId));
+      
+      LocalId localId = new LocalId(targetId);
+      TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+      
+      if (tx == null)
+      {
+         log.trace("RemoteException in commit: transaction not found");
+         throw new NoSuchObjectException("No transaction.");
+      }
+      
+      try
+      {
+         tx.commit(false);
+      }
+      catch (IllegalStateException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Exception: ", e);
+         TransactionNotPreparedException ex = 
+            new TransactionNotPreparedException();
+         ex.initCause(e);
+         throw ex;
+      }
+      catch (RollbackException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Exception: ", e);
+         TransactionRolledbackException ex = 
+            new TransactionRolledbackException(e.toString());
+         ex.detail = e;
+         throw ex;
+      }
+      catch (javax.transaction.HeuristicMixedException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Exception: ", e);
+         HeuristicMixedException ex = new HeuristicMixedException();
+         ex.initCause(e);
+         throw ex;
+      }
+      catch (javax.transaction.HeuristicRollbackException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Exception: ", e);
+         HeuristicRollbackException ex = new HeuristicRollbackException();
+         ex.initCause(e);
+         throw ex;
+      }
+      catch (SystemException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new UnexpectedException(e.toString(), e);
+      }
+   }
+
+   public void commitOnePhase(long targetId) 
+         throws RemoteException,
+                HeuristicHazardException
+   {
+      if (log.isTraceEnabled())
+         log.trace("Resource.commitOnePhase, targetId=" + 
+                   Long.toHexString(targetId));
+
+      LocalId localId = new LocalId(targetId);
+      TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+      
+      if (tx == null)
+      {
+         log.trace("RemoteException in commit: transaction not found");
+         throw new NoSuchObjectException("No transaction.");
+      }
+      
+      try
+      {
+         tx.commit(true);
+      }
+      catch (RollbackException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Exception: ", e);
+         TransactionRolledbackException ex = 
+            new TransactionRolledbackException(e.toString());
+         ex.detail = e;
+         throw ex;
+      }
+      catch (javax.transaction.HeuristicMixedException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Exception: ", e);
+         throw new UnexpectedException(e.toString(), e);
+      }
+      catch (javax.transaction.HeuristicRollbackException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Exception: ", e);
+         throw new UnexpectedException(e.toString(), e);
+      }
+      catch (SystemException e)
+      {
+         if (log.isTraceEnabled())
+            log.trace("Unexpected exception: ", e);
+         throw new UnexpectedException(e.toString(), e);
+      }
+  }
+
+   public void forget(long targetId)
+      throws RemoteException
+   {
+      if (log.isTraceEnabled())
+         log.trace("Resource.forget, targetId=" + Long.toHexString(targetId));
+      
+      LocalId localId = new LocalId(targetId);
+      TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+      
+      if (tx == null)
+      {
+         log.trace("RemoteException in forget: transaction not found");
+         throw new NoSuchObjectException("No transaction.");
+      }
+
+      tx.forget();
+   }
+
+   // IRecoveryCoordinator method -----------------------------------
+
+   public Status replayCompletion(long targetId, final Resource r)
+         throws RemoteException,
+                TransactionNotPreparedException
+   {
+      if (log.isTraceEnabled())
+         log.trace("RecoveryCoordinator.replayCompletion, targetId=" + 
+                Long.toHexString(targetId));
+      
+      TxManager tm = (TxManager) TMUtil.getTransactionManager();
+      if (tm.isRecoveryPending())
+      {
+         if (log.isTraceEnabled())
+            log.trace("RecoveryCoordinator.replayCompletion called on" +
+                      " targetId=" + Long.toHexString(targetId) + 
+                      " before recovery is complete.\n" +
+                      " Throwing RemoteException.");
+         throw new RemoteException("Transaction manager not ready.");
+      }
+      
+      LocalId localId = new LocalId(targetId);
+      TransactionImpl tx = (TransactionImpl)TMUtil.getTransaction(localId);
+      
+      if (tx == null)
+      {
+         log.trace("RemoteException in replayCompletion: " +
+                   "transaction not found");
+         throw new NoSuchObjectException("No transaction.");
+      }
+      
+      int status = tx.replayCompletion(r);
+      
+      if (status == javax.transaction.Status.STATUS_MARKED_ROLLBACK ||
+            status == javax.transaction.Status.STATUS_NO_TRANSACTION ||
+            status == javax.transaction.Status.STATUS_ROLLEDBACK ||
+            status == javax.transaction.Status.STATUS_ROLLING_BACK)
+      {
+         Runnable runnable =  new Runnable() 
+         {  
+            public void run()
+            {
+               try
+               {
+                  r.rollback();
+               }
+               catch (Exception ignore)
+               {
+                  // We can ignore this exception. If the resource didn't get
+                  // the rollback then it will eventually call replayCompletion
+                  // again.
+                  if (log.isTraceEnabled())
+                     log.trace("Ignoring exception in remote resource rollback", 
+                               ignore);
+               }
+            }
+         };
+         Thread t = new Thread(runnable, "resourceRollbackThread");
+         
+         t.start();
+      }
+      return javaxToJBoss(status);
+   }
+
+   // TODO: This class does not need to implement ISynchronization  
+   // ISynchronization methods --------------------------------------
+
+   public void beforeCompletion(long targetId)
+   {
+      // TODO: remove beforeCompletion
+      if (log.isTraceEnabled())
+         log.trace("Synchronization.beforeCompletion, targetId=" + 
+                Long.toHexString(targetId));
+   }
+
+   public void afterCompletion(long targetId)
+   {
+      // TODO: remove afterCompletion
+      if (log.isTraceEnabled())
+         log.trace("Synchronization.afterCompletion, targetId=" + 
+                Long.toHexString(targetId));
+   }
+
+   // CoordinatorFactory implementation -----------------------------
+
+   /**
+    * Creates a reference for the DTM object with the given interface and
+    * <code>localId</code>. 
+    * @see org.jboss.tm.ProxyFactory#create(java.lang.Class, long)
+    */
+   public Coordinator createCoordinator(long localId)
+   {
+      return (Coordinator) RemoteProxy.create(Coordinator.class, 
+                                              localId, 
+                                              dtm.getLocators());
+   }
+
+   // ResourceFactory implementation --------------------------------
+
+   /**
+    * Creates a reference for the DTM resource with the given 
+    * <code>localId</code>. 
+    * @see org.jboss.tm.ResourceFactory#create(long)
+    */
+   public Resource createResource(long localId)
+   {
+      return (Resource) RemoteProxy.create(Resource.class,
+                                           localId,
+                                           dtm.getLocators());
+   }
+   
+   // StringRemoteRefConverter implementation -----------------------
+   
+   /**
+    * Converts a stringfied reference to a remote resource back to a remote 
+    * reference.
+    *  
+    * @param strResource a stringfied reference to a remote resource
+    * @return a remote reference to the resource.
+    */
+   public Resource stringToResource(String strResource)
+   {
+      try
+      {
+         return (Resource) RemoteProxy.fromString(strResource);
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException(e);
+      }
+   }
+
+   /**
+    * Converts a stringfied reference to a remote recovery coordinator back 
+    * to a remote reference.
+    *  
+    * @param strRecCoordinator a stringfied reference to a remote recovery
+    *                          coordinator
+    * @return a remote reference to the recovery coordinator.
+    */
+   public RecoveryCoordinator stringToRecoveryCoordinator(
+                                                   String strRecCoordinator)
+   {
+      try
+      {
+         return (RecoveryCoordinator) RemoteProxy.fromString(strRecCoordinator);
+      }
+      catch (Exception e)
+      {
+         throw new RuntimeException(e);
+      }
+   }
+
+   /**
+    * Takes a remote reference to a resource and converts it to a string.
+    * 
+    * @param res a remote reference to a resource
+    * @return a string that represents the remote resource.
+    */
+   public String resourceToString(Resource res)
+   {
+      return RemoteProxy.toString((Proxy)res);
+   }
+
+   /**
+    * Takes a remote reference to recovery coordinator and converts it to a 
+    * string. 
+    * 
+    * @param recoveryCoord a remote reference to a recovery coordinator
+    * @return a string that represents the remote recovery coordinator.
+    */
+   public String recoveryCoordinatorToString(RecoveryCoordinator recoveryCoord)
+   {
+      return RemoteProxy.toString((Proxy)recoveryCoord);
+   }
+   
+   // Private -------------------------------------------------------
+
+   private static Status javaxToJBoss(int status)
+   {
+      return Status.fromInteger(status);
+   }
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManager.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManager.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManager.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,204 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.server;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.management.ObjectName;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+
+import javax.transaction.TransactionManager;
+
+import org.jboss.remoting.InvokerLocator;
+import org.jboss.system.ServiceMBeanSupport;
+import org.jboss.tm.TxManager;
+import org.jboss.tm.TMUtil;
+import org.jboss.tm.remoting.RemoteProxy;
+import org.jboss.tm.remoting.client.ClientUserTransaction;
+import org.jboss.tm.remoting.interfaces.TransactionFactory;
+
+/**
+ * Service MBean that implements the distributed transaction manager.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 57024 $ 
+ */
+public class DistributedTransactionManager extends ServiceMBeanSupport
+      implements DistributedTransactionManagerMBean
+{
+   // Constants -----------------------------------------------------
+   
+   public static final String SUBSYSTEM = "DTM"; 
+   
+   private static final String[] addInvocationHandlerSignature = 
+      new String[] {
+         "java.lang.String", 
+         "org.jboss.remoting.ServerInvocationHandler"
+      };
+   
+   private static final String[] removeInvocationHandlerSignature = 
+      new String[] { "java.lang.String" };
+   
+   private static final Object[] removeInvocationHandlerParams = 
+      new Object[] { SUBSYSTEM };
+
+   public static final String USER_TRANSACTION_JNDI_NAME = "UserTransaction";
+
+   // Attributes ----------------------------------------------------
+
+   private List connectors;
+   private InvokerLocator[] locators;
+   private String[] locatorURIs;
+   private boolean interpositionEnabled;
+   
+   // ServiceMBeanSupport overrides ---------------------------------
+
+   protected void startService()
+         throws Exception 
+   {
+      DTMServant dtmServant = new DTMServant(this);
+      List locatorList = new ArrayList();
+      List locatorURIList = new ArrayList();
+      Iterator i = connectors.iterator();
+
+      while (i.hasNext())
+      {
+         // Add DTM invocation handler to connector
+         ObjectName objectName = (ObjectName) i.next();
+         getServer().invoke(objectName, 
+                            "addInvocationHandler",
+                            new Object[] {SUBSYSTEM,
+                                          new DTMInvocationHandler(dtmServant)},
+                            addInvocationHandlerSignature);
+         getLog().debug("Added DTM invocation handler to connector " + 
+                        objectName);
+         
+         // Get the connector's invoker locator and locator URI   
+         InvokerLocator locator = 
+            (InvokerLocator) getServer().getAttribute(objectName, "Locator");
+         locatorList.add(locator);
+         String locatorURI = locator.getLocatorURI();
+         locatorURIList.add(locatorURI);
+      }
+      locators = (InvokerLocator[]) locatorList.toArray(new InvokerLocator[0]);
+      locatorURIs = (String[]) locatorURIList.toArray(new String[0]);
+      
+      // Set the TxManager's DTM CordinatorFactory and ResourceFactory. 
+      TransactionManager tm = TMUtil.getTransactionManager();
+      
+      if (tm instanceof TxManager)
+      {
+         TxManager txManager = (TxManager)tm;
+         txManager.setDTMEnabled(true);
+         txManager.setDTMCoordinatorFactory(dtmServant);
+         txManager.setDTMResourceFactory(dtmServant);
+         txManager.setDTMStringRemoteRefConverter(dtmServant);
+         txManager.setInterpositionEnabled(interpositionEnabled);
+      }
+      
+      // Bind the DTM TransactionFactory proxy into JNDI 
+      Context ctx = new InitialContext();
+      TransactionFactory transactionFactory =
+         (TransactionFactory) RemoteProxy.create(TransactionFactory.class, 
+                                                 0, 
+                                                 locators);
+      ctx.bind(ClientUserTransaction.TX_FACTORY_JNDI_NAME, transactionFactory);
+
+      // Bind the UserTransaction reference into JNDI 
+      ctx.bind(USER_TRANSACTION_JNDI_NAME, 
+               ClientUserTransaction.getSingleton());
+   } 
+ 
+   protected void stopService()
+         throws Exception 
+   {
+      // Unset the TxManager's DTM ResourceFactory. 
+      TxManager tm = (TxManager)TMUtil.getTransactionManager();
+      tm.setDTMResourceFactory(null);
+
+      Iterator i = connectors.iterator();
+      
+      while (i.hasNext())
+      {
+         // Remove DTM invocation handler from connector
+         ObjectName objectName = (ObjectName) i.next();
+         getServer().invoke(objectName, 
+                            "removeInvocationHandler",
+                            removeInvocationHandlerParams,
+                            removeInvocationHandlerSignature);
+         getLog().debug("Removed DTM invocation handler from connector " + 
+                        objectName);
+      }
+
+      // Unset the TxManager's DTM CordinatorFactory and ResourceFactory. 
+      tm.setDTMEnabled(false);
+      tm.setDTMCoordinatorFactory(null);
+      tm.setDTMResourceFactory(null);
+      
+      // Unbind the DTM TransactionFactory proxy 
+      // and the UserTransaction reference from JNDI 
+      Context ctx = new InitialContext();
+      ctx.unbind(ClientUserTransaction.TX_FACTORY_JNDI_NAME);
+      ctx.unbind(USER_TRANSACTION_JNDI_NAME);
+   }
+
+   // DistributedTransactionManagerMBean methods --------------------
+
+   public List getConnectors()
+   {
+      return connectors;
+   }
+   
+   public void setConnectors(List connectors)
+   {
+      this.connectors  = connectors; 
+   }
+
+   public InvokerLocator[] getLocators()
+   {
+      return locators;
+   }
+   
+   public String[] getLocatorURIs()
+   {
+      return locatorURIs;
+   }
+
+   public boolean getInterpositionEnabled()
+   {
+      return interpositionEnabled;
+   }
+
+   public void setInterpositionEnabled(boolean newValue)
+   {
+      interpositionEnabled = newValue;
+      if (getState() == STARTED)
+      {
+         TxManager tm = (TxManager)TMUtil.getTransactionManager();
+         tm.setInterpositionEnabled(newValue);
+      }
+   }
+
+}

Added: trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManagerMBean.java
===================================================================
--- trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManagerMBean.java	                        (rev 0)
+++ trunk/transaction/src/main/org/jboss/tm/remoting/server/DistributedTransactionManagerMBean.java	2007-10-04 14:34:28 UTC (rev 65842)
@@ -0,0 +1,49 @@
+/*
+  * JBoss, Home of Professional Open Source
+  * Copyright 2005, JBoss Inc., and individual contributors as indicated
+  * by the @authors tag. See the copyright.txt in the distribution for a
+  * full listing of individual contributors.
+  *
+  * This is free software; you can redistribute it and/or modify it
+  * under the terms of the GNU Lesser General Public License as
+  * published by the Free Software Foundation; either version 2.1 of
+  * the License, or (at your option) any later version.
+  *
+  * This software is distributed in the hope that it will be useful,
+  * but WITHOUT ANY WARRANTY; without even the implied warranty of
+  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  * Lesser General Public License for more details.
+  *
+  * You should have received a copy of the GNU Lesser General Public
+  * License along with this software; if not, write to the Free
+  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+  * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
+  */
+package org.jboss.tm.remoting.server;
+
+import java.util.List;
+
+import org.jboss.remoting.InvokerLocator;
+import org.jboss.system.ServiceMBean;
+
+/**
+ * MBean interface exposed by the DTM.
+ *
+ * @author <a href="mailto:reverbel at ime.usp.br">Francisco Reverbel</a>
+ * @version $Revision: 37459 $ 
+ */
+public interface DistributedTransactionManagerMBean
+   extends ServiceMBean
+{
+   List getConnectors();
+   
+   void setConnectors(List connectors);
+   
+   InvokerLocator[] getLocators();
+   
+   String[] getLocatorURIs();
+   
+   boolean getInterpositionEnabled();
+   
+   void setInterpositionEnabled(boolean newValue);
+}




More information about the jboss-cvs-commits mailing list