[jboss-svn-commits] JBL Code SVN: r21296 - in labs/jbosstm/trunk/XTS: WS-C and 51 other directories.

jboss-svn-commits at lists.jboss.org jboss-svn-commits at lists.jboss.org
Wed Jul 30 13:08:39 EDT 2008


Author: adinn
Date: 2008-07-30 13:08:39 -0400 (Wed, 30 Jul 2008)
New Revision: 21296

Added:
   labs/jbosstm/trunk/XTS/WS-T/dev/src/org/
   labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/
   labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/
   labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/
   labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/
   labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/
   labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/
   labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/ATParticipantHelper.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/ATParticipantRecoveryRecord.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/PersistableATParticipant.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/XTSATRecoveryManager.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/XTSATRecoveryModule.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src10/org/
   labs/jbosstm/trunk/XTS/WS-T/dev/src10/org/jboss/
   labs/jbosstm/trunk/XTS/WS-T/dev/src10/org/jboss/jbossts/
   labs/jbosstm/trunk/XTS/WS-T/dev/src10/org/jboss/jbossts/xts10/
   labs/jbosstm/trunk/XTS/WS-T/dev/src10/org/jboss/jbossts/xts10/recovery/
   labs/jbosstm/trunk/XTS/WS-T/dev/src10/org/jboss/jbossts/xts10/recovery/participant/
   labs/jbosstm/trunk/XTS/WS-T/dev/src10/org/jboss/jbossts/xts10/recovery/participant/at/
   labs/jbosstm/trunk/XTS/WS-T/dev/src10/org/jboss/jbossts/xts10/recovery/participant/at/ATParticipantRecoveryRecord.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src11/org/jboss/
   labs/jbosstm/trunk/XTS/WS-T/dev/src11/org/jboss/jbossts/
   labs/jbosstm/trunk/XTS/WS-T/dev/src11/org/jboss/jbossts/xts11/
   labs/jbosstm/trunk/XTS/WS-T/dev/src11/org/jboss/jbossts/xts11/recovery/
   labs/jbosstm/trunk/XTS/WS-T/dev/src11/org/jboss/jbossts/xts11/recovery/participant/
   labs/jbosstm/trunk/XTS/WS-T/dev/src11/org/jboss/jbossts/xts11/recovery/participant/at/
   labs/jbosstm/trunk/XTS/WS-T/dev/src11/org/jboss/jbossts/xts11/recovery/participant/at/ATParticipantRecoveryRecord.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/recovery/
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/recovery/DemoATRecoveryListener.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/recovery/DemoATRecoveryModule.java
   labs/jbosstm/trunk/XTS/docs/XTSRecoveryNotes.txt
   labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/
   labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/logging/
   labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/logging/XTSLogger.java
   labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/participant/
   labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/participant/at/
   labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/participant/at/ATParticipantRecoveryModule.java
   labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/participant/at/XTSATRecoveryManagerImple.java
Removed:
   labs/jbosstm/trunk/XTS/sar/src/org/jboss/transactions/
Modified:
   labs/jbosstm/trunk/XTS/WS-C/build.xml
   labs/jbosstm/trunk/XTS/WS-T/build.xml
   labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/webservices/wsat/processors/ParticipantProcessor.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/CoordinatorProcessorImpl.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/ParticipantProcessorImpl.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/engines/CoordinatorEngine.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/engines/ParticipantEngine.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/webservices11/wsat/processors/ParticipantProcessor.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/CoordinatorProcessorImpl.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/ParticipantProcessorImpl.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/engines/CoordinatorEngine.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/engines/ParticipantEngine.java
   labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/stub/ParticipantStub.java
   labs/jbosstm/trunk/XTS/WS-T/tests/src/com/arjuna/wst/tests/junit/TestParticipantProcessor.java
   labs/jbosstm/trunk/XTS/WSAS/build.xml
   labs/jbosstm/trunk/XTS/WSCF/build.xml
   labs/jbosstm/trunk/XTS/WSTX/build.xml
   labs/jbosstm/trunk/XTS/build.xml
   labs/jbosstm/trunk/XTS/demo/dd/jboss/service-web-app.xml
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantManager.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantParticipantAT.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantServiceAT.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantView.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiManager.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiParticipantAT.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiServiceAT.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiView.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreManager.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreParticipantAT.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreServiceAT.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreView.java
   labs/jbosstm/trunk/XTS/sar/META-INF/jboss-beans.xml
   labs/jbosstm/trunk/XTS/sar/build.xml
   labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/XTSService.java
   labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/XTSServiceMBean.java
   labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/ACCoordinatorRecoveryModule.java
   labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/Implementations.java
   labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/ParticipantRecordSetup.java
   labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/RecoverACCoordinator.java
Log:
version of XTS which includes participant and coordinator side recovery for WS-AT (with minimal testing). also includes version of XTS 1.1 demo which implements durable WS-AT participants as required for recovery to work -- fixes for JBTM-385

Modified: labs/jbosstm/trunk/XTS/WS-C/build.xml
===================================================================
--- labs/jbosstm/trunk/XTS/WS-C/build.xml	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WS-C/build.xml	2008-07-30 17:08:39 UTC (rev 21296)
@@ -80,7 +80,7 @@
     <property name="xts.ext.jars" value=""/>
     <property name="jboss.client.lib.jars" value="jaxb-api.jar jboss-javaee.jar jbossws-native-jaxrpc.jar
             jbossws-native-jaxws.jar jbossws-client.jar stax-api.jar jbossws-native-saaj.jar
-            jbossws-native-jaxws-ext.jar jbossws-native-core.jar jbossws-common.jar"/>
+            jbossws-native-jaxws-ext.jar jbossws-native-core.jar jbossws-common.jar jboss-logging-spi.jar"/>
 
     <property name="tests.libs" value="junit.jar"/>
 

Modified: labs/jbosstm/trunk/XTS/WS-T/build.xml
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/build.xml	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WS-T/build.xml	2008-07-30 17:08:39 UTC (rev 21296)
@@ -83,7 +83,7 @@
     <property name="jta.ext.jars" value="jbossts-common.jar commons-logging-1.1.jar"/>
     <property name="jboss.client.lib.jars" value="jaxb-api.jar jboss-javaee.jar jbossws-native-jaxrpc.jar
             jbossws-native-jaxws.jar jbossws-client.jar stax-api.jar jbossws-native-saaj.jar
-            jbossws-native-jaxws-ext.jar jbossws-native-core.jar jbossws-common.jar"/>
+            jbossws-native-jaxws-ext.jar jbossws-native-core.jar jbossws-common.jar jboss-logging-spi.jar"/>
 
     <property name="tests.libs"              value="junit.jar"/>
     <property name="ws-c.libs"               value="ws-c.jar"/>
@@ -195,7 +195,7 @@
 
         <classpath-builder filename="${com.arjuna.mwlabs.classpathbuilderfilename}" inproperty="build.classpath"/>
 
-        <javadoc    packagenames="com.arjuna.*"
+        <javadoc    packagenames="com.arjuna.*, org.jboss.jbossts.xts.*"
                     failonerror="yes"
                     private="yes"
                     defaultexcludes="yes"
@@ -203,14 +203,17 @@
 
             <packageset dir="${dev.src.dir}" defaultexcludes="yes">
               <include name="com/arjuna/**"/>
+              <include name="org/jboss/jbossts/xts/**"/>
             </packageset>
 
             <packageset dir="${dev.src10.dir}" defaultexcludes="yes">
               <include name="com/arjuna/**"/>
+              <include name="org/jboss/jbossts/xts/**"/>
             </packageset>
 
             <packageset dir="${dev.src11.dir}" defaultexcludes="yes">
               <include name="com/arjuna/**"/>
+              <include name="org/jboss/jbossts/xts//**"/>
             </packageset>
 
              <doclet name="com.hp.mw.buildsystem.doclet.resbundledoclet.ResourceBundleDoclet">

Added: labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/ATParticipantHelper.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/ATParticipantHelper.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/ATParticipantHelper.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -0,0 +1,55 @@
+package org.jboss.jbossts.xts.recovery.participant.at;
+
+import com.arjuna.wst.Durable2PCParticipant;
+
+import java.io.Serializable;
+import java.io.ByteArrayOutputStream;
+import java.io.ObjectOutputStream;
+
+/**
+ *  helper to support retrieval of durable AT participant recovery state from participant
+ */
+public class ATParticipantHelper
+{
+    /**
+     * obtain a byte array containing the recovery state associated with the supplied
+     * participant.
+     * @param useSerialization true if the object should be converted to a byte array using
+     * serialization otherwise it will be converted by casting to the PersistableATParticipant
+     * interface and employing the getRecoveryState method.
+     * @param participant the participant whose recovery state is to be obtained
+     * @return the state to be saved ro null if no state needs to be saved
+     * @throws Exception an exception occurred generating the required recoverable state
+     */
+    public static byte[] getRecoveryState(boolean useSerialization, Durable2PCParticipant participant)
+            throws Exception
+    {
+        if (useSerialization) {
+            // serialize the object to a byte array via an object output stream
+
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream() ;
+            final ObjectOutputStream oos = new ObjectOutputStream(baos) ;
+            oos.writeObject(participant) ;
+            oos.flush() ;
+            return baos.toByteArray();
+        } else {
+            PersistableATParticipant persistableParticipant = (PersistableATParticipant) participant;
+            return persistableParticipant.getRecoveryState();
+        }
+    }
+
+    /**
+     * return true if the object can be saved and restored using serialization otherwise
+     * return false
+     * @param participant
+     * @return
+     */
+    public static boolean isSerializable(Durable2PCParticipant participant)
+    {
+        if (participant instanceof Serializable) {
+            return true;
+        }
+
+        return false;
+    }
+}

Added: labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/ATParticipantRecoveryRecord.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/ATParticipantRecoveryRecord.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/ATParticipantRecoveryRecord.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -0,0 +1,197 @@
+package org.jboss.jbossts.xts.recovery.participant.at;
+
+import com.arjuna.wst.PersistableParticipant;
+import com.arjuna.wst.Durable2PCParticipant;
+import com.arjuna.ats.arjuna.state.OutputObjectState;
+import com.arjuna.ats.arjuna.state.InputObjectState;
+import com.arjuna.webservices.logging.WSTLogger;
+
+import javax.xml.stream.XMLStreamException;
+import java.io.*;
+
+/**
+ * asbstract class used to implement save, recover and reactivate API for durable
+ * XTS participants. this is subclassed by both a 1.0 and a 1.1 specific class because
+ * the activate operation needs to create a participant engine appropriate to the
+ * protocol in use when the participant was saved.
+ */
+public abstract class ATParticipantRecoveryRecord implements PersistableParticipant {
+
+    /**
+     * construct the protocol-independent part of a WS-AT participant recovery record
+     * @param id
+     * @param participant
+     */
+    protected ATParticipantRecoveryRecord(String id, Durable2PCParticipant participant)
+    {
+        this.id = id;
+        this.participant = participant;
+        recoveryState = null;
+        recoveryStateValid = false;
+    }
+
+    /**
+     * Retrieve and save the state of the particpant to the specified input object stream.
+     * @param oos The output output stream.
+     * @return true if persisted, false otherwise.
+     *
+     * @message org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryRecord.saveState_1 [org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryRecord.saveState_1] Could not save recovery state for son-serializable durable WS-AT participant {0}
+     * @message org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryRecord.saveState_2 [org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryRecord.saveState_2] XML stream exception saving recovery state for participant {0}
+     * @message org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryRecord.saveState_3 [org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryRecord.saveState_3] I/O exception saving recovery state for participant {0}
+     */
+    public final boolean saveState(OutputObjectState oos) {
+        if (participant == null) {
+            return false;
+        }
+
+        try {
+            useSerialization = ATParticipantHelper.isSerializable(participant);
+            recoveryState = ATParticipantHelper.getRecoveryState(useSerialization, participant);
+        } catch (Exception exception) {
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryRecord.saveState_1", new Object[] {id}) ;
+            }
+            return false;
+        }
+
+        try {
+            oos.packString(id);
+            saveEndpointReference(oos);
+            oos.packBoolean(useSerialization);
+            if (recoveryState != null) {
+                oos.packBoolean(true);
+                oos.packBytes(recoveryState);
+            } else {
+                oos.packBoolean(false);
+            }
+        } catch (XMLStreamException xmle) {
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryRecord.saveState_2", new Object[] {id}, xmle) ;
+            }
+            return false;
+        } catch (IOException ioe) {
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryRecord.saveState_3", new Object[] {id}, ioe) ;
+            }
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Restore the state of the particpant from the specified input object stream.
+     *
+     * @param ios The Input object stream.
+     * @return true if restored, false otherwise.
+     *
+     * @message org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryRecord.restoreState_1 [org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryRecord.restoreState_1] XML stream exception restoring recovery state for participant {0}
+     * @message org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryRecord.restoreState_2 [org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryRecord.restoreState_2] I/O exception saving restoring state for participant {0}
+     */
+    public boolean restoreState(InputObjectState ios) {
+        try {
+            id = ios.unpackString();
+            restoreEndpointReference(ios);
+            useSerialization = ios.unpackBoolean();
+            if (ios.unpackBoolean()) {
+                recoveryState = ios.unpackBytes();
+            } else {
+                recoveryState =  null;
+            }
+            recoveryStateValid = true;
+        } catch (XMLStreamException xmle) {
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryRecord.restoreState_1", new Object[] {id}, xmle) ;
+            }
+            return false;
+        } catch (IOException ioe) {
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryRecord.restoreState_2", new Object[] {id}, ioe) ;
+            }
+            return false;
+        }
+        
+        return true;
+    }
+
+    /**
+     * called during recovery processing to attempt to convert the restored application-
+     * specific recovery state back into a participant
+     * @param module the XTS recovery module to be used to attempt the conversion
+     * @return
+     */
+
+    public boolean restoreParticipant(XTSATRecoveryModule module) throws Exception
+    {
+        if (participant != null) {
+            // don't think this should ever happen
+            return false;
+        }
+
+        if (useSerialization) {
+            final ByteArrayInputStream bais = new ByteArrayInputStream(recoveryState) ;
+            final ObjectInputStream ois = new ObjectInputStream(bais) ;
+
+            participant = module.deserialize(getId(), ois);
+        } else {
+            participant = module.recreate(getId(), recoveryState);
+        }
+
+        if (participant != null) {
+            return true;
+        }
+
+        return false;
+    }
+
+    public String getId()
+    {
+        return id;
+    }
+
+    /**
+     * return the path string under which AT participant records are to be located in the TX
+     * object store
+     * @return
+     */
+    public static String type ()
+    {
+        return type;
+    }
+
+    /**
+     * save the endpoint reference to the coordinator for this participant
+     */
+    protected abstract void saveEndpointReference(OutputObjectState oos) throws IOException, XMLStreamException;
+
+    /**
+     * restore the endpoint reference to the coordinator for this participant
+     */
+    protected abstract void restoreEndpointReference(InputObjectState ios) throws IOException, XMLStreamException;
+
+    /**
+     * create a participant engine to manage commit or rollback processing for the
+     * participant and install it in the active participants table
+     */
+    public abstract void activate();
+
+    /**
+     * test whether a participant is currently activated with the id of this recovery record.
+     *
+     * @return true if a participant is currently activated with the id of this recovery record
+     */
+    public abstract boolean isActive();
+
+    protected Durable2PCParticipant participant;
+    protected String id;
+    private boolean useSerialization;
+    private byte[] recoveryState;
+    private boolean recoveryStateValid;
+    final private static String type = "/XTS/WSAT/ParticipantRecoveryRecord";
+}
+

Added: labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/PersistableATParticipant.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/PersistableATParticipant.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/PersistableATParticipant.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -0,0 +1,11 @@
+package org.jboss.jbossts.xts.recovery.participant.at;
+
+/**
+ * an interface which can be implemented by AT participants which wish to save recovery state at prepare
+ * which they can subsequently use to restore the participant state if a crash happnes bteween prepare and
+ * commit/rollback
+ */
+public interface PersistableATParticipant
+{
+    byte[] getRecoveryState() throws Exception;
+}

Added: labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/XTSATRecoveryManager.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/XTSATRecoveryManager.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/XTSATRecoveryManager.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -0,0 +1,146 @@
+package org.jboss.jbossts.xts.recovery.participant.at;
+
+import com.arjuna.ats.arjuna.common.Uid;
+
+import java.util.NoSuchElementException;
+
+/**
+ * Abstract class defining API for managing both paritcipant and coordinator recovery
+ * On hteparticipant this is responsible for saving WS-AT participant recovery records
+ * during prepare, deleting them at commit and recreating and reactivating them during
+ * recovery.
+ * On the coordinator side it currently merely records whether coordinator recovery has
+ * copleted its first scan
+ */
+public abstract class XTSATRecoveryManager {
+    /*****************************************************************************************/
+    /* get or set the AT recovery manager singleton                                          */
+    /*****************************************************************************************/
+
+    /**
+     * obtain a reference to the WS-AT recovery manager singleton instance
+     * @return the singleton instance
+     */
+    public static XTSATRecoveryManager getRecoveryManager()
+    {
+        return theRecoveryManager;
+    }
+
+    /**
+     * set the WS-AT recovery manager singleton instance
+     * @param recoveryManager the instance to use as the recovery manager
+     * @return the singleton previously in use or null if it was not previously set
+     */
+    public static XTSATRecoveryManager setRecoveryManager(XTSATRecoveryManager recoveryManager)
+    {
+        XTSATRecoveryManager old = theRecoveryManager;
+        theRecoveryManager = recoveryManager;
+
+        return old;
+    }
+
+    /*****************************************************************************************/
+    /* API used by application client code to register XTS recovery modules at application   */
+    /* startup and to unregister them when the application is unloaded                       */
+    /*****************************************************************************************/
+
+    /**
+     * register an application specific recovery module which acts as a helper to recreate
+     * a WS-AT durable participant from the participant's recovery data saved at prepare
+     * @param module the module which will be used to identify and recreate participants
+     * for the application
+     * @throws NullPointerException if the supplied module is null
+     */
+    public abstract void registerRecoveryModule(XTSATRecoveryModule module);
+
+    /**
+     * unregister an application specific recovery module previously registered as
+     * a helper to recretae WS-AT durable participants
+     * @param module the module to be unregistered
+     * @throws NoSuchElementException if the module is not currently registered
+     */
+    public abstract void unregisterRecoveryModule(XTSATRecoveryModule module) throws NoSuchElementException;
+
+    /*****************************************************************************************/
+    /* API used by participant service to create and delete participant recovery records in  */
+    /* persistent store during normal operation                                              */
+    /*****************************************************************************************/
+
+    /**
+     * save the supplied participant recovery record to persistent storage
+     * @param participantRecoveryRecord
+     */
+    public abstract boolean writeParticipantRecoveryRecord(ATParticipantRecoveryRecord participantRecoveryRecord);
+
+    /**
+     * remove any participant recovery record with the supplied id from persistent storage
+     * @param id
+     */
+    public abstract boolean deleteParticipantRecoveryRecord(String id);
+
+    /*****************************************************************************************/
+    /* API used by recovery scanning thread to add entries to the AT recovery manager's      */
+    /* table of recovered participant recovery records.                                      */
+    /*****************************************************************************************/
+
+    /**
+     * test whether the supplied uid identifies an active participant or a recovered but inactive
+     * participant
+     * @param uid
+     */
+    public abstract boolean isParticipantPresent(Uid uid);
+
+    /**
+     * add a recovered participant record to the table of unrecovered participants which
+     * need to be recreated following recovery
+     *
+     * @param uid the uid under which the participant was saved in the file store
+     * @param participantRecoveryRecord the in-memory represenattion of the recovery record
+     * saved to disk
+     */
+    public abstract void addParticipantRecoveryRecord(Uid uid, ATParticipantRecoveryRecord participantRecoveryRecord);
+
+    /**
+     * see if a participant recovery record with a given id exists in the table of unrecovered
+     * participants
+     * @param id the identifier of the participant being sought
+     * @return the participant recovery record with the supplied id or null if it is not found
+     */
+    public abstract ATParticipantRecoveryRecord findParticipantRecoveryRecord(String id);
+
+    /**
+     * process all entries in the recovered participant map and attempt to recreate the
+     * application participant and activate it
+     */
+    public abstract void recoverParticipants();
+
+    /**
+     * test whether the first AT participant recovery scan has completed. this indicates whether
+     * there may or may not still be unknown participant recovery records on disk. If the first
+     * scan has not yet completed then a commit or rollback message for an unknown participant
+     * must be dropped. If it has then a commit or rollback for an unknown participant must be
+     * acknowledged with, respectively, a committed or aborted message.
+     */
+    public abstract boolean isParticipantRecoveryStarted();
+
+    /**
+     * test whether the first AT coordinator recovery scan has completed. this indicates whether
+     * there may or may not still be unknown AT transcation records on disk. If the first
+     * scan has not yet completed then a prepare message for an unknown participant
+     * must be dropped. If it has then a perpare for an unknown participant must be
+     * acknowledged with a rollback message.
+     */
+    public abstract boolean isCoordinatorRecoveryStarted();
+
+    /**
+     * record the fact thatwhether the first AT coordinator recovery scan has completed.
+     */
+
+    public abstract void setCoordinatorRecoveryStarted();
+
+    /**
+     * the singleton instance of the recovery manager
+     */
+
+    private static XTSATRecoveryManager theRecoveryManager = null;
+}

Added: labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/XTSATRecoveryModule.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/XTSATRecoveryModule.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src/org/jboss/jbossts/xts/recovery/participant/at/XTSATRecoveryModule.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -0,0 +1,39 @@
+package org.jboss.jbossts.xts.recovery.participant.at;
+
+import com.arjuna.wst.Durable2PCParticipant;
+
+import java.io.ObjectInputStream;
+
+/**
+ * an interface implemented by applications which wish to be involved in recovering
+ * saved participants after a crash.
+ */
+public interface XTSATRecoveryModule
+{
+    /**
+     * called during recovery processing to allow an application to identify a participant id
+     * belonging to one of its participants and recreate the participant by deserializing
+     * it from the supplied object input stream. n.b. this is only appropriate in case the
+     * participant was originally saved using serialization.
+     * @param id the id used when the participant was created
+     * @param stream a stream from which the application should deserialise the participant
+     * if it recognises that the id belongs to the module's application
+     * @return
+     * @throws Exception if an error occurs deserializing the durable participant
+     */
+    public Durable2PCParticipant deserialize(String id, ObjectInputStream stream) throws Exception;
+
+    /**
+     * called during recovery processing to allow an application to identify a participant id
+     * belonging to one of its participants and use the saved recovery state to recreate the
+     * participant. n.b. this is only appropriate in case the participant was originally saved
+     * after being converted to a byte array using the PersistibleATParticipant interface.
+     * @param id the id used when the participant was created
+     * @param recoveryState a byte array returned form the original participant via a call to
+     * method getRecoveryState of interface PersistableATParticipant
+     * @return
+     * @throws Exception if an error occurs converting the recoveryState back to a
+     * durable participant
+     */
+    public Durable2PCParticipant recreate(String id, byte[] recoveryState) throws Exception;
+}

Modified: labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/webservices/wsat/processors/ParticipantProcessor.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/webservices/wsat/processors/ParticipantProcessor.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/webservices/wsat/processors/ParticipantProcessor.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -72,6 +72,12 @@
     public abstract void deactivateParticipant(final ParticipantInboundEvents participant) ;
 
     /**
+     * Check whether a participant with the given id is currently active
+     * @param identifier The identifier.
+     */
+    public abstract boolean isActive(final String identifier) ;
+
+    /**
      * Commit.
      * @param commit The commit notification.
      * @param addressingContext The addressing context.

Modified: labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/CoordinatorProcessorImpl.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/CoordinatorProcessorImpl.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/CoordinatorProcessorImpl.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -36,6 +36,7 @@
 import com.arjuna.webservices.wsat.processors.CoordinatorProcessor;
 import com.arjuna.webservices.wscoor.CoordinationConstants;
 import com.arjuna.wsc.messaging.MessageId;
+import org.jboss.jbossts.xts.recovery.participant.at.XTSATRecoveryManager;
 
 /**
  * The Coordinator processor.
@@ -403,15 +404,6 @@
     }
 
     /**
-     * Notifies that all coordinator entries in the recovery log have been accounted for.
-     */
-
-    public static void setRecoveryLogEntriesAccountedFor()
-    {
-        recoveryLogEntriesAccountedFor = true;
-    }
-
-    /**
      * Tests if there may be unknown coordinator entries in the recovery log.
      *
      * @return false if there may be unknown coordinator entries in the recovery log.
@@ -419,18 +411,6 @@
 
     private static boolean areRecoveryLogEntriesAccountedFor()
     {
-        return recoveryLogEntriesAccountedFor;
+        return XTSATRecoveryManager.getRecoveryManager().isCoordinatorRecoveryStarted();
     }
-
-    /**
-     * False if there may be unknown coordinator entries in the recovery log otherwise true.
-     * This field defaults to false at boot. It is reset to true when the first log scan has
-     * completed from which point onwards there will always be a record in the activation
-     * processor for each entry in the recovery log.
-     *
-     * @return False if there may be unknown coordinator entries in the recovery log otherwise
-     * true.
-     */
-
-    private static boolean recoveryLogEntriesAccountedFor = false;
 }

Modified: labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/ParticipantProcessorImpl.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/ParticipantProcessorImpl.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/ParticipantProcessorImpl.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -31,6 +31,7 @@
 import com.arjuna.webservices.wsat.client.CoordinatorClient;
 import com.arjuna.webservices.wsat.processors.ParticipantProcessor;
 import com.arjuna.wsc.messaging.MessageId;
+import org.jboss.jbossts.xts.recovery.participant.at.XTSATRecoveryManager;
 
 /**
  * The Participant processor.
@@ -63,6 +64,17 @@
     }
     
     /**
+     * Check whether a participant with the given id is currently active
+     * @param identifier The identifier.
+     */
+    public boolean isActive(final String identifier)
+    {
+        // if there is an entry in the table then it is active or completed and pending delete
+
+        return (activatedObjectProcessor.getObject(identifier) != null);
+    }
+
+    /**
      * Get the participant with the specified identifier.
      * @param instanceIdentifier The participant identifier.
      * @return The participant or null if not known.
@@ -81,13 +93,32 @@
      * 
      * @message com.arjuna.wst.messaging.ParticipantProcessorImpl.commit_1 [com.arjuna.wst.messaging.ParticipantProcessorImpl.commit_1] - Unexpected exception thrown from commit:
      * @message com.arjuna.wst.messaging.ParticipantProcessorImpl.commit_2 [com.arjuna.wst.messaging.ParticipantProcessorImpl.commit_2] - Commit called on unknown participant: {0}
+     * @message com.arjuna.wst.messaging.ParticipantProcessorImpl.commit_3 [com.arjuna.wst.messaging.ParticipantProcessorImpl.commit_3] - Commit request dropped pending WS-AT participant recovery manager initialization for participant: {0}
+     * @message com.arjuna.wst.messaging.ParticipantProcessorImpl.commit_4 [com.arjuna.wst.messaging.ParticipantProcessorImpl.commit_4] - Commit request dropped pending WS-AT participant recovery manager scan for unknown participant: {0}
+     * @message com.arjuna.wst.messaging.ParticipantProcessorImpl.commit_5 [com.arjuna.wst.messaging.ParticipantProcessorImpl.commit_5] - Commit request dropped pending registration of application-specific recovery module for WS-AT participant: {0}
      */
     public void commit(final NotificationType commit, final AddressingContext addressingContext,
         final ArjunaContext arjunaContext)
     {
         final InstanceIdentifier instanceIdentifier = arjunaContext.getInstanceIdentifier() ;
+
+        /**
+         * ensure the AT participant recovery manager is running
+         */
+        XTSATRecoveryManager recoveryManager = XTSATRecoveryManager.getRecoveryManager();
+
+        if (recoveryManager == null) {
+            // log warning and drop this message -- it will be resent
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("com.arjuna.wst.messaging.ParticipantProcessorImpl.commit_3", new Object[] {instanceIdentifier}) ;
+            }
+
+            return;
+        }
+
         final ParticipantInboundEvents participant = getParticipant(instanceIdentifier) ;
-        
+
         if (participant != null)
         {
             try
@@ -102,6 +133,20 @@
                 }
             }
         }
+        else if (!recoveryManager.isParticipantRecoveryStarted())
+        {
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("com.arjuna.wst.messaging.ParticipantProcessorImpl.commit_4", new Object[] {instanceIdentifier}) ;
+            }
+        }
+        else if (recoveryManager.findParticipantRecoveryRecord(instanceIdentifier.getInstanceIdentifier()) != null)
+        {
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("com.arjuna.wst.messaging.ParticipantProcessorImpl.commit_5", new Object[] {instanceIdentifier}) ;
+            }
+        }
         else
         {
             if (WSTLogger.arjLoggerI18N.isWarnEnabled())
@@ -120,6 +165,7 @@
      * 
      * @message com.arjuna.wst.messaging.ParticipantProcessorImpl.prepare_1 [com.arjuna.wst.messaging.ParticipantProcessorImpl.prepare_1] - Unexpected exception thrown from prepare: 
      * @message com.arjuna.wst.messaging.ParticipantProcessorImpl.prepare_2 [com.arjuna.wst.messaging.ParticipantProcessorImpl.prepare_2] - Prepare called on unknown participant: {0}
+     * @message com.arjuna.wst.messaging.ParticipantProcessorImpl.prepare_3 [com.arjuna.wst.messaging.ParticipantProcessorImpl.prepare_3] - Prepare request dropped pending WS-AT participant recovery manager initialization for participant: {0}
      */
     public void prepare(final NotificationType prepare, final AddressingContext addressingContext,
         final ArjunaContext arjunaContext)
@@ -159,11 +205,29 @@
      * 
      * @message com.arjuna.wst.messaging.ParticipantProcessorImpl.rollback_1 [com.arjuna.wst.messaging.ParticipantProcessorImpl.rollback_1] - Unexpected exception thrown from rollback: 
      * @message com.arjuna.wst.messaging.ParticipantProcessorImpl.rollback_2 [com.arjuna.wst.messaging.ParticipantProcessorImpl.rollback_2] - Rollback called on unknown participant: {0}
+     * @message com.arjuna.wst.messaging.ParticipantProcessorImpl.rollback_3 [com.arjuna.wst.messaging.ParticipantProcessorImpl.rollback_3] - Rollback request dropped pending WS-AT participant recovery manager initialization for participant: {0}
+     * @message com.arjuna.wst.messaging.ParticipantProcessorImpl.rollback_4 [com.arjuna.wst.messaging.ParticipantProcessorImpl.rollback_4] - Rollback request dropped pending WS-AT participant recovery manager scan for unknown participant: {0}
+     * @message com.arjuna.wst.messaging.ParticipantProcessorImpl.rollback_5 [com.arjuna.wst.messaging.ParticipantProcessorImpl.rollback_5] - Rollback request dropped pending registration of application-specific recovery module for WS-AT participant: {0}
      */
     public void rollback(final NotificationType rollback, final AddressingContext addressingContext,
         final ArjunaContext arjunaContext)
     {
         final InstanceIdentifier instanceIdentifier = arjunaContext.getInstanceIdentifier() ;
+
+        /**
+         * ensure the AT participant recovery manager is running
+         */
+        XTSATRecoveryManager recoveryManager = XTSATRecoveryManager.getRecoveryManager();
+
+        if (recoveryManager == null) {
+            // log warning and drop this message -- it will be resent
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("com.arjuna.wst.messaging.ParticipantProcessorImpl.rollback_3", new Object[] {instanceIdentifier}) ;
+            }
+
+        }
+
         final ParticipantInboundEvents participant = getParticipant(instanceIdentifier) ;
 
         if (participant != null)
@@ -180,6 +244,20 @@
                 }
             }
         }
+        else if (!recoveryManager.isParticipantRecoveryStarted())
+        {
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("com.arjuna.wst.messaging.ParticipantProcessorImpl.rollback_4", new Object[] {instanceIdentifier}) ;
+            }
+        }
+        else if (recoveryManager.findParticipantRecoveryRecord(instanceIdentifier.getInstanceIdentifier()) != null)
+        {
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("com.arjuna.wst.messaging.ParticipantProcessorImpl.rollback_5", new Object[] {instanceIdentifier}) ;
+            }
+        }
         else
         {
             if (WSTLogger.arjLoggerI18N.isWarnEnabled())

Modified: labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/engines/CoordinatorEngine.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/engines/CoordinatorEngine.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/engines/CoordinatorEngine.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -109,7 +109,15 @@
         this.recovered = recovered;
         this.state = state ;
 
-        CoordinatorProcessor.getProcessor().activateCoordinator(this, id) ;
+        // unrecovered participants are always activated
+        // we only need to reactivate recovered participants which were successfully prepared
+        // any others will only have been saved because of a heuristic outcome e.g. a comms
+        // timeout at prepare will write a heuristic record for an ABORTED TX including a
+        // participant in state PREPARING. we can safely drop it since we implement presumed abort.
+
+        if (!recovered || state == State.STATE_PREPARED_SUCCESS) {
+            CoordinatorProcessor.getProcessor().activateCoordinator(this, id) ;
+        }
     }
     
     /**
@@ -337,6 +345,14 @@
 
                 timerTask = null;
             }
+
+            // ok, the coordinator is going to start a rollback because of this timeout but it will
+            // only roll back the participants which have been prepared or have not yet been processed.
+            // we need to deactivate the participant here so that any later prepared messages are
+            // answered with a rollback (presumed abort)
+
+            CoordinatorProcessor.getProcessor().deactivateCoordinator(this);
+
             return state ;
         }
     }
@@ -386,11 +402,15 @@
                 return state ;
             }
 
-            // the participant is still uncommitted so it must be rewritten to the log.
+            // the participant is still uncommitted so it will be rewritten to the log.
             // it remains activated in case a committed message comes in between now and
             // the next scan. the recovery code will detect this active participant when
             // rescanning the log and use it instead of recreating a new one.
+            // we need to mark this one as recovered so it does not get deleted until
+            // the next scan
 
+            recovered = true;
+
             return State.STATE_COMMITTING;
         }
     }

Modified: labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/engines/ParticipantEngine.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/engines/ParticipantEngine.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src10/com/arjuna/wst/messaging/engines/ParticipantEngine.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -39,12 +39,9 @@
 import com.arjuna.webservices.wsat.processors.ParticipantProcessor;
 import com.arjuna.webservices.wscoor.CoordinationConstants;
 import com.arjuna.wsc.messaging.MessageId;
-import com.arjuna.wst.Aborted;
-import com.arjuna.wst.Participant;
-import com.arjuna.wst.Prepared;
-import com.arjuna.wst.ReadOnly;
-import com.arjuna.wst.SystemException;
-import com.arjuna.wst.Vote;
+import com.arjuna.wst.*;
+import org.jboss.jbossts.xts10.recovery.participant.at.ATParticipantRecoveryRecord;
+import org.jboss.jbossts.xts.recovery.participant.at.XTSATRecoveryManager;
 
 /**
  * The participant state engine
@@ -72,8 +69,13 @@
      * The associated timer task or null.
      */
     private TimerTask timerTask ;
-    
+
     /**
+     * true id this is a recovered participant otherwise false.
+     */
+    private boolean recovered ;
+
+    /**
      * Construct the initial engine for the participant.
      * @param participant The participant.
      * @param id The participant id.
@@ -81,7 +83,7 @@
      */
     public ParticipantEngine(final Participant participant, final String id, final EndpointReferenceType coordinator)
     {
-        this(participant, id, State.STATE_ACTIVE, coordinator) ;
+        this(participant, id, State.STATE_ACTIVE, coordinator, false) ;
     }
     
     /**
@@ -91,12 +93,13 @@
      * @param state The initial state.
      * @param coordinator The coordinator endpoint reference.
      */
-    public ParticipantEngine(final Participant participant, final String id, final State state, final EndpointReferenceType coordinator)
+    public ParticipantEngine(final Participant participant, final String id, final State state, final EndpointReferenceType coordinator, boolean recovered)
     {
         this.participant = participant ;
         this.id = id ;
         this.state = state ;
         this.coordinator = coordinator ;
+        this.recovered = recovered;
     }
     
     /**
@@ -324,12 +327,37 @@
         
         if (current == State.STATE_PREPARING)
         {
+            if (participant instanceof Durable2PCParticipant) {
+                // write a durable participant recovery record to the persistent store
+                Durable2PCParticipant durableParticipant =(Durable2PCParticipant) participant;
+
+                ATParticipantRecoveryRecord recoveryRecord = new ATParticipantRecoveryRecord(id, durableParticipant, coordinator);
+                if (!XTSATRecoveryManager.getRecoveryManager().writeParticipantRecoveryRecord(recoveryRecord)) {
+                    // we need to revert the state to PREPARING
+                    state = State.STATE_PREPARING;
+                    return;
+                }
+            }
+
             sendPrepared() ;
         }
         else if (current == State.STATE_COMMITTING)
         {
-            sendCommitted() ;
-            forget() ;
+            if (participant instanceof Durable2PCParticipant) {
+                // remove any durable participant recovery record from the persistent store
+                Durable2PCParticipant durableParticipant =(Durable2PCParticipant) participant;
+
+                // if we cannot delete the participant we effectively drop the commit message
+                // here in the hope that we have better luck next time. the delete call will
+                // already have logged a warning so there is no else branch here.
+                if (XTSATRecoveryManager.getRecoveryManager().deleteParticipantRecoveryRecord(id)) {
+                    sendCommitted();
+                    forget();
+                }
+            } else {
+                sendCommitted() ;
+                forget() ;
+            }
         }
     }
     
@@ -375,7 +403,7 @@
                 state = State.STATE_ABORTING ;
             }
         }
-        
+
         if ((current == State.STATE_PREPARING) || (current == State.STATE_ACTIVE))
         {
             sendAborted() ;

Added: labs/jbosstm/trunk/XTS/WS-T/dev/src10/org/jboss/jbossts/xts10/recovery/participant/at/ATParticipantRecoveryRecord.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src10/org/jboss/jbossts/xts10/recovery/participant/at/ATParticipantRecoveryRecord.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src10/org/jboss/jbossts/xts10/recovery/participant/at/ATParticipantRecoveryRecord.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -0,0 +1,89 @@
+package org.jboss.jbossts.xts10.recovery.participant.at;
+
+import com.arjuna.wst.Durable2PCParticipant;
+import com.arjuna.wst.messaging.engines.ParticipantEngine;
+import com.arjuna.ats.arjuna.state.OutputObjectState;
+import com.arjuna.ats.arjuna.state.InputObjectState;
+import com.arjuna.webservices.wsaddr.EndpointReferenceType;
+import com.arjuna.webservices.wsat.State;
+import com.arjuna.webservices.wsat.processors.ParticipantProcessor;
+
+import javax.xml.stream.*;
+import java.io.StringWriter;
+import java.io.IOException;
+import java.io.StringReader;
+
+/**
+ * recovery record specific to WS-AT 1.0 protocol participants. this implements the behaviours
+ * necessary to save and restore a 1.0 participant to or from the TX object store
+ */
+public class ATParticipantRecoveryRecord extends org.jboss.jbossts.xts.recovery.participant.at.ATParticipantRecoveryRecord {
+
+    /**
+     * constructor used during prepare processing to create an AT 1.0 participant record
+     * for saving to the object store
+     * @param id the id of the application-specific participant
+     * @param participant the application-specific participant
+     * @param
+     */
+    public ATParticipantRecoveryRecord(String id, Durable2PCParticipant participant, EndpointReferenceType endpoint)
+    {
+        super(id, participant);
+        this.endpoint = endpoint;
+    }
+
+    /**
+     * constructor used during recovery processing to create a record whose contents will be
+     * recovered from the object store
+     */
+    public ATParticipantRecoveryRecord()
+    {
+        super(null, null);
+    }
+
+    /**
+     * save the endpoint reference to the coordinator for this participant
+     */
+    protected void saveEndpointReference(OutputObjectState oos) throws IOException, XMLStreamException{
+        // save an XML representation of the endpoint
+        XMLOutputFactory factory = XMLOutputFactory.newInstance();
+        StringWriter stringWriter = new StringWriter();
+        XMLStreamWriter writer = factory.createXMLStreamWriter(stringWriter);
+        endpoint.writeContent(writer);
+        writer.close();
+        oos.packString(stringWriter.toString());
+    }
+
+    /**
+     * restore the endpoint reference to the coordinator for this participant
+     */
+    protected void restoreEndpointReference(InputObjectState ios) throws IOException, XMLStreamException{
+        // restore and parse the XML representation of the endpoint
+        String xmlEndpoint = ios.unpackString();
+        StringReader stringReader = new StringReader(xmlEndpoint);
+        XMLInputFactory factory = XMLInputFactory.newInstance();
+        XMLStreamReader reader = factory.createXMLStreamReader(stringReader);
+        endpoint = new EndpointReferenceType(reader);
+    }
+
+    /**
+     * create a participant engine to manage commit or rollback processing for the
+     * participant and install it in the active participants table
+     */
+    public void activate() {
+        ParticipantEngine engine = new ParticipantEngine(participant, id, State.STATE_PREPARED_SUCCESS, endpoint, true);
+        ParticipantProcessor.getProcessor().activateParticipant(engine, getId());
+    }
+
+    /**
+     * test whether a participant is currently activated with the id of this recovery record.
+     *
+     * @return true if a participant is currently activated with the id of this recovery record
+     */
+    public boolean isActive()
+    {
+        return ParticipantProcessor.getProcessor().isActive(getId());
+    }
+
+    private EndpointReferenceType endpoint;
+}

Modified: labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/webservices11/wsat/processors/ParticipantProcessor.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/webservices11/wsat/processors/ParticipantProcessor.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/webservices11/wsat/processors/ParticipantProcessor.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -54,6 +54,12 @@
     public abstract void deactivateParticipant(final ParticipantInboundEvents participant) ;
 
     /**
+     * Check whether a participant with the given id is currently active
+     * @param identifier The identifier.
+     */
+    public abstract boolean isActive(final String identifier) ;
+
+    /**
      * Commit.
      * @param commit The commit notification.
      * @param addressingProperties The addressing context.

Modified: labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/CoordinatorProcessorImpl.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/CoordinatorProcessorImpl.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/CoordinatorProcessorImpl.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -16,6 +16,7 @@
 import com.arjuna.webservices11.ServiceRegistry;
 import com.arjuna.wsc11.messaging.MessageId;
 import org.oasis_open.docs.ws_tx.wsat._2006._06.Notification;
+import org.jboss.jbossts.xts.recovery.participant.at.XTSATRecoveryManager;
 
 import javax.xml.ws.addressing.AddressingProperties;
 
@@ -327,15 +328,6 @@
     }
 
     /**
-     * Notifies that all coordinator entries in the recovery log have been accounted for.
-     */
-
-    public static void setRecoveryLogEntriesAccountedFor()
-    {
-        recoveryLogEntriesAccountedFor = true;
-    }
-
-    /**
      * Tests if there may be unknown coordinator entries in the recovery log.
      *
      * @return false if there may be unknown coordinator entries in the recovery log.
@@ -343,18 +335,6 @@
 
     private static boolean areRecoveryLogEntriesAccountedFor()
     {
-        return recoveryLogEntriesAccountedFor;
+        return XTSATRecoveryManager.getRecoveryManager().isCoordinatorRecoveryStarted();
     }
-
-    /**
-     * False if there may be unknown coordinator entries in the recovery log otherwise true.
-     * This field defaults to false at boot. It is reset to true when the first log scan has
-     * completed from which point onwards there will always be a record in the activation
-     * processor for each entry in the recovery log.
-     *
-     * @return False if there may be unknown coordinator entries in the recovery log otherwise
-     * true.
-     */
-
-    private static boolean recoveryLogEntriesAccountedFor = false;
 }

Modified: labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/ParticipantProcessorImpl.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/ParticipantProcessorImpl.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/ParticipantProcessorImpl.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -11,6 +11,7 @@
 import com.arjuna.webservices11.wsat.client.CoordinatorClient;
 import com.arjuna.wsc11.messaging.MessageId;
 import org.oasis_open.docs.ws_tx.wsat._2006._06.Notification;
+import org.jboss.jbossts.xts.recovery.participant.at.XTSATRecoveryManager;
 
 import javax.xml.ws.addressing.AddressingProperties;
 
@@ -45,6 +46,17 @@
     }
 
     /**
+     * Check whether a participant with the given id is currently active
+     * @param identifier The identifier.
+     */
+    public boolean isActive(final String identifier)
+    {
+        // if there is an entry in the table then it is active or completed and pending delete
+
+        return (activatedObjectProcessor.getObject(identifier) != null);
+    }
+
+    /**
      * Get the participant with the specified identifier.
      * @param instanceIdentifier The participant identifier.
      * @return The participant or null if not known.
@@ -63,11 +75,30 @@
      *
      * @message com.arjuna.wst11.messaging.ParticipantProcessorImpl.commit_1 [com.arjuna.wst11.messaging.ParticipantProcessorImpl.commit_1] - Unexpected exception thrown from commit:
      * @message com.arjuna.wst11.messaging.ParticipantProcessorImpl.commit_2 [com.arjuna.wst11.messaging.ParticipantProcessorImpl.commit_2] - Commit called on unknown participant: {0}
+     * @message com.arjuna.wst11.messaging.ParticipantProcessorImpl.commit_3 [com.arjuna.wst11.messaging.ParticipantProcessorImpl.commit_3] - Commit request dropped pending WS-AT participant recovery manager initialization for participant: {0}
+     * @message com.arjuna.wst11.messaging.ParticipantProcessorImpl.commit_4 [com.arjuna.wst11.messaging.ParticipantProcessorImpl.commit_4] - Commit request dropped pending WS-AT participant recovery manager scan for unknown participant: {0}
+     * @message com.arjuna.wst11.messaging.ParticipantProcessorImpl.commit_5 [com.arjuna.wst11.messaging.ParticipantProcessorImpl.commit_5] - Commit request dropped pending registration of application-specific recovery module for WS-AT participant: {0}
      */
     public void commit(final Notification commit, final AddressingProperties addressingProperties,
         final ArjunaContext arjunaContext)
     {
         final InstanceIdentifier instanceIdentifier = arjunaContext.getInstanceIdentifier() ;
+
+        /**
+         * ensure the AT participant recovery manager is running
+         */
+        XTSATRecoveryManager recoveryManager = XTSATRecoveryManager.getRecoveryManager();
+
+        if (recoveryManager == null) {
+            // log warning and drop this message -- it will be resent
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("com.arjuna.wst11.messaging.ParticipantProcessorImpl.commit_3", new Object[] {instanceIdentifier}) ;
+            }
+
+            return;
+        }
+
         final ParticipantInboundEvents participant = getParticipant(instanceIdentifier) ;
 
         if (participant != null)
@@ -84,6 +115,20 @@
                 }
             }
         }
+        else if (!recoveryManager.isParticipantRecoveryStarted())
+        {
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("com.arjuna.wst11.messaging.ParticipantProcessorImpl.commit_4", new Object[] {instanceIdentifier}) ;
+            }
+        }
+        else if (recoveryManager.findParticipantRecoveryRecord(instanceIdentifier.getInstanceIdentifier()) != null)
+        {
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("com.arjuna.wst11.messaging.ParticipantProcessorImpl.commit_5", new Object[] {instanceIdentifier}) ;
+            }
+        }
         else
         {
             if (WSTLogger.arjLoggerI18N.isWarnEnabled())
@@ -141,11 +186,29 @@
      *
      * @message com.arjuna.wst11.messaging.ParticipantProcessorImpl.rollback_1 [com.arjuna.wst11.messaging.ParticipantProcessorImpl.rollback_1] - Unexpected exception thrown from rollback:
      * @message com.arjuna.wst11.messaging.ParticipantProcessorImpl.rollback_2 [com.arjuna.wst11.messaging.ParticipantProcessorImpl.rollback_2] - Rollback called on unknown participant: {0}
+     * @message com.arjuna.wst11.messaging.ParticipantProcessorImpl.rollback_3 [com.arjuna.wst11.messaging.ParticipantProcessorImpl.rollback_3] - Rollback request dropped pending WS-AT participant recovery manager initialization for participant: {0}
+     * @message com.arjuna.wst11.messaging.ParticipantProcessorImpl.rollback_4 [com.arjuna.wst11.messaging.ParticipantProcessorImpl.rollback_4] - Rollback request dropped pending WS-AT participant recovery manager scan for unknown participant: {0}
+     * @message com.arjuna.wst11.messaging.ParticipantProcessorImpl.rollback_5 [com.arjuna.wst11.messaging.ParticipantProcessorImpl.rollback_5] - Rollback request dropped pending registration of application-specific recovery module for WS-AT participant: {0}
      */
     public void rollback(final Notification rollback, final AddressingProperties addressingProperties,
         final ArjunaContext arjunaContext)
     {
         final InstanceIdentifier instanceIdentifier = arjunaContext.getInstanceIdentifier() ;
+
+        /**
+         * ensure the AT participant recovery manager is running
+         */
+        XTSATRecoveryManager recoveryManager = XTSATRecoveryManager.getRecoveryManager();
+
+        if (recoveryManager == null) {
+            // log warning and drop this message -- it will be resent
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("com.arjuna.wst11.messaging.ParticipantProcessorImpl.rollback_3", new Object[] {instanceIdentifier}) ;
+            }
+
+        }
+
         final ParticipantInboundEvents participant = getParticipant(instanceIdentifier) ;
 
         if (participant != null)
@@ -162,6 +225,20 @@
                 }
             }
         }
+        else if (!recoveryManager.isParticipantRecoveryStarted())
+        {
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("com.arjuna.wst11.messaging.ParticipantProcessorImpl.rollback_4", new Object[] {instanceIdentifier}) ;
+            }
+        }
+        else if (recoveryManager.findParticipantRecoveryRecord(instanceIdentifier.getInstanceIdentifier()) != null)
+        {
+            if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                WSTLogger.arjLoggerI18N.warn("com.arjuna.wst11.messaging.ParticipantProcessorImpl.rollback_5", new Object[] {instanceIdentifier}) ;
+            }
+        }
         else
         {
             if (WSTLogger.arjLoggerI18N.isWarnEnabled())

Modified: labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/engines/CoordinatorEngine.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/engines/CoordinatorEngine.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/engines/CoordinatorEngine.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -87,7 +87,15 @@
         this.state = state ;
         this.recovered = recovered;
 
-        CoordinatorProcessor.getProcessor().activateCoordinator(this, id) ;
+        // unrecovered participants are always activated
+        // we only need to reactivate recovered participants which were successfully prepared
+        // any others will only have been saved because of a heuristic outcome e.g. a comms
+        // timeout at prepare will write a heuristic record for an ABORTED TX including a
+        // participant in state PREPARING. we can safely drop it since we implement presumed abort.
+
+        if (!recovered || state == State.STATE_PREPARED_SUCCESS) {
+            CoordinatorProcessor.getProcessor().activateCoordinator(this, id) ;
+        }
     }
 
     /**
@@ -279,6 +287,14 @@
 
                 timerTask = null;
             }
+
+            // ok, the coordinator is going to start a rollback because of this timeout but it will
+            // only roll back the participants which have been prepared or have not yet been processed.
+            // we need to deactivate the participant here so that any later prepared messages are
+            // answered with a rollback (presumed abort)
+
+            CoordinatorProcessor.getProcessor().deactivateCoordinator(this);
+            
             return state ;
         }
     }
@@ -328,11 +344,15 @@
                 return state ;
             }
 
-            // the participant is still uncommitted so it must be rewritten to the log.
+            // the participant is still uncommitted so it will be rewritten to the log.
             // it remains activated in case a committed message comes in between now and
             // the next scan. the recovery code will detect this active participant when
             // rescanning the log and use it instead of recreating a new one.
+            // we need to mark this one as recovered so it does not get deleted until
+            // the next scan
 
+            recovered = true;
+
             return State.STATE_COMMITTING;
         }
     }
@@ -440,6 +460,15 @@
     }
 
     /**
+     * Retrieve the current state of this participant
+     * @return the current state.
+     */
+    public synchronized State getState()
+    {
+        return state;
+    }
+
+    /**
      * Change the state and notify any listeners.
      * @param state The new state.
      */

Modified: labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/engines/ParticipantEngine.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/engines/ParticipantEngine.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/messaging/engines/ParticipantEngine.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -11,12 +11,12 @@
 import com.arjuna.webservices11.wsat.client.CoordinatorClient;
 import com.arjuna.webservices11.wsat.ParticipantInboundEvents;
 import com.arjuna.webservices11.wsat.State;
-import com.arjuna.webservices11.wsat.AtomicTransactionConstants;
 import com.arjuna.webservices11.wsat.processors.ParticipantProcessor;
-import com.arjuna.webservices11.ServiceRegistry;
 import com.arjuna.wsc11.messaging.MessageId;
 import com.arjuna.wst.*;
 import org.oasis_open.docs.ws_tx.wsat._2006._06.Notification;
+import org.jboss.jbossts.xts11.recovery.participant.at.ATParticipantRecoveryRecord;
+import org.jboss.jbossts.xts.recovery.participant.at.XTSATRecoveryManager;
 
 import javax.xml.namespace.QName;
 import javax.xml.ws.addressing.AddressingProperties;
@@ -49,8 +49,17 @@
      * The associated timer task or null.
      */
     private TimerTask timerTask ;
+    /**
+     * true if this participant has been recovered otherwise false
+     */
+    private boolean recovered;
 
     /**
+     * true if this participant's recovery details have been logged to disk otherwise false
+     */
+    private boolean persisted;
+
+    /**
      * Construct the initial engine for the participant.
      * @param participant The participant.
      * @param id The participant id.
@@ -58,7 +67,7 @@
      */
     public ParticipantEngine(final Participant participant, final String id, final W3CEndpointReference coordinator)
     {
-        this(participant, id, State.STATE_ACTIVE, coordinator) ;
+        this(participant, id, State.STATE_ACTIVE, coordinator, false) ;
     }
 
     /**
@@ -68,12 +77,14 @@
      * @param state The initial state.
      * @param coordinator The coordinator endpoint reference.
      */
-    public ParticipantEngine(final Participant participant, final String id, final State state, final W3CEndpointReference coordinator)
+    public ParticipantEngine(final Participant participant, final String id, final State state, final W3CEndpointReference coordinator, final boolean recovered)
     {
         this.participant = participant ;
         this.id = id ;
         this.state = state ;
         this.coordinator = coordinator ;
+        this.recovered = recovered;
+        this.persisted = recovered;
     }
 
     /**
@@ -171,6 +182,8 @@
      * PreparedSuccess -> Aborting (execute rollback, send aborted and forget)
      * Committing -> Committing (ignore)
      * Aborting -> Aborting (send aborted and forget)
+     *
+     *  @message com.arjuna.wst11.messaging.engines.ParticipantEngine.rollback_1 [com.arjuna.wst11.messaging.engines.ParticipantEngine.rollback_1] could not delete recovery record for participant {0}
      */
     public void rollback(final Notification rollback, final AddressingProperties addressingProperties, final ArjunaContext arjunaContext)
     {
@@ -178,6 +191,7 @@
         synchronized(this)
         {
             current = state ;
+
             if ((current == State.STATE_ACTIVE) || (current == State.STATE_PREPARING) ||
                 (current == State.STATE_PREPARED_SUCCESS))
             {
@@ -190,12 +204,36 @@
             if ((current == State.STATE_ACTIVE) || (current == State.STATE_PREPARING) ||
                 (current == State.STATE_PREPARED_SUCCESS))
             {
+                // n.b. if state is PREPARING the participant may still be in the middle
+                // of prepare or may even be told to prepare after this is called. according
+                // to the spec that is not our lookout. however, rollback should only get
+                // called once here.
+
                 if (!executeRollback())
                 {
                     return ;
                 }
             }
 
+            // if the participant managed to persist the log record then we should try
+            // to delete it. note that persisted can only be set to true by the PREPARING
+            // thread. if it detects a transtiion to ABORTING while it is doing the log write
+            // it will clear up itself.
+
+            if (persisted && participant instanceof Durable2PCParticipant) {
+                // if we cannot delete the participant we effectively drop the rollback message
+                // here in the hope that we have better luck next time..
+                if (!XTSATRecoveryManager.getRecoveryManager().deleteParticipantRecoveryRecord(id)) {
+                    // hmm, could not delete entry -- leave it so we can maybe retry later
+                    if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+                    {
+                        WSTLogger.arjLoggerI18N.warn("com.arjuna.wst11.messaging.engines.ParticipantEngine.rollback_1", new Object[] {id}) ;
+                    }
+
+                    return;
+                }
+            }
+
             sendAborted() ;
 
             if (current != null)
@@ -288,13 +326,21 @@
      *
      * Preparing -> PreparedSuccess (send Prepared)
      * Committing -> Committing (send committed and forget)
+
+     * @message com.arjuna.wst11.messaging.engines.ParticipantEngine.commitDecision_1 [com.arjuna.wst11.messaging.engines.ParticipantEngine.commitDecision_1] - Exception rolling back participant
+     * @message com.arjuna.wst11.messaging.engines.ParticipantEngine.commitDecision_2 [com.arjuna.wst11.messaging.engines.ParticipantEngine.commitDecision_2] - Unable to delete recovery record during prepare for participant {0}
+     * @message com.arjuna.wst11.messaging.engines.ParticipantEngine.commitDecision_3 [com.arjuna.wst11.messaging.engines.ParticipantEngine.commitDecision_3] - Unable to delete recovery record at commit for participant {0}
      */
     private void commitDecision()
     {
-        final State current ;
+        State current ;
+        boolean rollbackRequired  = false;
+        boolean deleteRequired  = false;
+
         synchronized(this)
         {
             current = state ;
+
             if (current == State.STATE_PREPARING)
             {
                 state = State.STATE_PREPARED_SUCCESS ;
@@ -303,10 +349,94 @@
 
         if (current == State.STATE_PREPARING)
         {
-            sendPrepared() ;
+            // ok, we need to write the recovery details to log and send prepared.
+            // if we cannot write the log then we have to rollback the participant
+            //  and send aborted.
+            if (participant instanceof Durable2PCParticipant) {
+                // write a durable participant recovery record to the persistent store
+                Durable2PCParticipant durableParticipant =(Durable2PCParticipant) participant;
+
+                ATParticipantRecoveryRecord recoveryRecord = new ATParticipantRecoveryRecord(id, durableParticipant, coordinator);
+
+                if (!XTSATRecoveryManager.getRecoveryManager().writeParticipantRecoveryRecord(recoveryRecord)) {
+                    // we need to rollback and send aborted unless some other thread
+                    //gets there first
+                    rollbackRequired = true;
+                }
+            }
+            // recheck state in case a rollback or readonly came in while we were writing the
+            // log record
+
+            synchronized (this) {
+                current = state;
+
+                if (current == State.STATE_PREPARED_SUCCESS) {
+                    if (rollbackRequired) {
+                        // if we change state to aborting then we are responsible for
+                        // calling rollback and sending aborted but we have no log record
+                        // to delete
+                        state = State.STATE_ABORTING;
+                    } else {
+                        // this ensures any subsequent commit or rollback deletes the log record
+                        // so we still have no log record to delete here
+                        persisted = true;
+                    }
+                } else if (!rollbackRequired) {
+                    // an incoming rollback or readonly changed the state to aborted or null so
+                    // it will already have performed a rollback if required but we need to
+                    // delete the log record since the rollback/readonly thread did not know
+                    // about it
+                    deleteRequired = true;
+                }
+            }
+
+            if (rollbackRequired)
+            {
+                // we need to do the rollback and send aborted
+
+                executeRollback();
+
+                sendAborted();
+                forget();
+            } else if (deleteRequired) {
+                // just try to delete the log entry -- any required aborted has already been sent
+
+                if (!XTSATRecoveryManager.getRecoveryManager().deleteParticipantRecoveryRecord(id)) {
+                    // hmm, could not delete entry log warning
+                    if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+                    {
+                        WSTLogger.arjLoggerI18N.warn("com.arjuna.wst11.messaging.engines.ParticipantEngine.commitDecision_2", new Object[] {id}) ;
+                    }
+                }
+            } else {
+                // whew got through -- send a prepared
+                sendPrepared() ;
+            }
         }
         else if (current == State.STATE_COMMITTING)
         {
+            if (persisted && participant instanceof Durable2PCParticipant) {
+                // remove any durable participant recovery record from the persistent store
+                Durable2PCParticipant durableParticipant =(Durable2PCParticipant) participant;
+
+                // if we cannot delete the participant we effectively drop the commit message
+                // here in the hope that we have better luck next time.
+                if (!XTSATRecoveryManager.getRecoveryManager().deleteParticipantRecoveryRecord(id)) {
+                    // hmm, could not delete entry -- log a warning
+                    if (WSTLogger.arjLoggerI18N.isWarnEnabled())
+                    {
+                        WSTLogger.arjLoggerI18N.warn("com.arjuna.wst11.messaging.engines.ParticipantEngine.commitDecision_3", new Object[] {id}) ;
+                    }
+                    // now revert back to PREPARED_SUCCESS and drop message awaiting a retry
+
+                    synchronized (this) {
+                        state = State.STATE_PREPARED_SUCCESS;
+                    }
+
+                    return;
+                }
+            }
+            
             sendCommitted() ;
             forget() ;
         }

Modified: labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/stub/ParticipantStub.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/stub/ParticipantStub.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src11/com/arjuna/wst11/stub/ParticipantStub.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -158,6 +158,20 @@
         {
             oos.packString(coordinator.getId()) ;
             oos.packBoolean(coordinator.isDurable()) ;
+            State state = coordinator.getState();
+            // participants in the heuristic list may get saved in any state
+            if (state == State.STATE_ACTIVE) {
+                oos.packInt(0);
+            } else if (state == State.STATE_PREPARING) {
+                oos.packInt(1);
+            } else if (state == State.STATE_PREPARED ||
+                        state == State.STATE_PREPARED_SUCCESS) {
+                oos.packInt(2);
+            } else if (state == State.STATE_ABORTING) {
+                oos.packInt(3);
+            } else { // COMMITTING or none
+                oos.packInt(4);
+            }
 
             // n.b. just use toString() for the endpoint -- it uses the writeTo() method which calls a suitable marshaller
             final StringWriter sw = new StringWriter() ;
@@ -187,10 +201,30 @@
      */
     public boolean restoreState(final InputObjectState ios)
     {
+        State state;
         try
         {
             final String id = ios.unpackString() ;
             final boolean durable = ios.unpackBoolean() ;
+            final int stateTag = ios.unpackInt();
+            switch (stateTag)
+            {
+                case 0:
+                    state = State.STATE_ACTIVE;
+                    break;
+                case 1:
+                    state = State.STATE_PREPARING;
+                    break;
+                case 2:
+                    state = State.STATE_PREPARED_SUCCESS;
+                    break;
+                case 3:
+                    state = State.STATE_ABORTING;
+                    break;
+                default:
+                    state = State.STATE_COMMITTING;
+                    break;
+            }
             final String eprValue = ios.unpackString() ;
 
             // this should successfully reverse the save process
@@ -199,12 +233,15 @@
             String eprefText = reader.getElementText();
             StreamSource source = new StreamSource(new StringReader(eprefText));
             final W3CEndpointReference endpointReference = new W3CEndpointReference(source);
-            // if we already have a coordinator from a previous recovery scan then reuse it
-            // with luck it will have been committed between the last scan and this one
+            // if we already have a coordinator from a previous recovery scan or because
+            // we had a heuristic outcoe then reuse it with luck it will have been committed
+            // or aborted between the last scan and this one
+            // note that whatever happens it will not have been removed from the table
+            // because it is marked as recovered
             coordinator = (CoordinatorEngine)CoordinatorProcessorImpl.getProcessor().getCoordinator(id);
             if (coordinator == null) {
-                // no entry found so recreate one which is at the prepared stage
-                coordinator = new CoordinatorEngine(id, durable, endpointReference, true, State.STATE_PREPARED_SUCCESS) ;
+                // no entry found so recreate one with the saved state
+                coordinator = new CoordinatorEngine(id, durable, endpointReference, true, state) ;
             }
             return true ;
         }

Added: labs/jbosstm/trunk/XTS/WS-T/dev/src11/org/jboss/jbossts/xts11/recovery/participant/at/ATParticipantRecoveryRecord.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/dev/src11/org/jboss/jbossts/xts11/recovery/participant/at/ATParticipantRecoveryRecord.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src11/org/jboss/jbossts/xts11/recovery/participant/at/ATParticipantRecoveryRecord.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -0,0 +1,82 @@
+package org.jboss.jbossts.xts11.recovery.participant.at;
+
+import com.arjuna.wst.Durable2PCParticipant;
+import com.arjuna.wst11.messaging.engines.ParticipantEngine;
+import com.arjuna.ats.arjuna.state.OutputObjectState;
+import com.arjuna.ats.arjuna.state.InputObjectState;
+import com.arjuna.webservices11.wsat.State;
+import com.arjuna.webservices11.wsat.processors.ParticipantProcessor;
+
+import javax.xml.ws.wsaddressing.W3CEndpointReference;
+import javax.xml.transform.Source;
+import javax.xml.transform.stream.StreamSource;
+import java.io.IOException;
+import java.io.StringReader;
+
+/**
+ * recovery record specific to WS-AT 1.1 protocol participants. this implements the behaviours
+ * necessary to save and restore a 1.1 participant to or from the TX object store
+ */
+public class ATParticipantRecoveryRecord extends org.jboss.jbossts.xts.recovery.participant.at.ATParticipantRecoveryRecord {
+
+    /**
+     * constructor used during prepare processing to create an AT 1.1 participant record
+     * for saving to the object store
+     * @param id the id of the application-specific participant
+     * @param participant the application-specific participant
+     * @param
+     */
+    public ATParticipantRecoveryRecord(String id, Durable2PCParticipant participant, W3CEndpointReference endpoint)
+    {
+        super(id, participant);
+        this.endpoint = endpoint;
+    }
+
+    /**
+     * constructor used during recovery processing to create a record whose contents will be
+     * recovered from the object store
+     */
+    public ATParticipantRecoveryRecord()
+    {
+        super(null, null);
+    }
+
+    /**
+     * save the endpoint reference to the coordinator for this participant
+     */
+    protected void saveEndpointReference(OutputObjectState oos) throws IOException {
+        // the toString method will do what we need
+        oos.packString(endpoint.toString());
+    }
+
+    /**
+     * restore the endpoint reference to the coordinator for this participant
+     */
+    protected void restoreEndpointReference(InputObjectState ios) throws IOException {
+        String endpointString = ios.unpackString();
+        Source source = new StreamSource(new StringReader(endpointString));
+        endpoint = new W3CEndpointReference(source);
+    }
+
+    /**
+     * create a participant engine to manage commit or rollback processing for the
+     * participant and install it in the active participants table
+     */
+    public void activate() {
+        ParticipantEngine engine = new ParticipantEngine(participant, id, State.STATE_PREPARED_SUCCESS, endpoint, true);
+        ParticipantProcessor.getProcessor().activateParticipant(engine, getId());
+    }
+
+    /**
+     * test whether a participant is currently activated with the id of this recovery record.
+     *
+     * @return true if a participant is currently activated with the id of this recovery record
+     */
+    public boolean isActive()
+    {
+        return ParticipantProcessor.getProcessor().isActive(getId());
+    }
+
+
+    private W3CEndpointReference endpoint;
+}
\ No newline at end of file

Modified: labs/jbosstm/trunk/XTS/WS-T/tests/src/com/arjuna/wst/tests/junit/TestParticipantProcessor.java
===================================================================
--- labs/jbosstm/trunk/XTS/WS-T/tests/src/com/arjuna/wst/tests/junit/TestParticipantProcessor.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WS-T/tests/src/com/arjuna/wst/tests/junit/TestParticipantProcessor.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -82,6 +82,11 @@
         //To change body of implemented methods use File | Settings | File Templates.
     }
 
+    public boolean isActive(final String identifier)
+    {
+        return true;
+    }
+
     public void commit(NotificationType commit,
             AddressingContext addressingContext, ArjunaContext arjunaContext)
     {

Modified: labs/jbosstm/trunk/XTS/WSAS/build.xml
===================================================================
--- labs/jbosstm/trunk/XTS/WSAS/build.xml	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WSAS/build.xml	2008-07-30 17:08:39 UTC (rev 21296)
@@ -52,7 +52,7 @@
     <property name="jta.ext.jars" value="jbossts-common.jar"/>
     <property name="jboss.client.lib.jars" value="jaxb-api.jar jboss-javaee.jar jbossws-native-jaxrpc.jar
             jbossws-native-jaxws.jar jbossws-client.jar stax-api.jar jbossws-native-saaj.jar
-            jbossws-native-jaxws-ext.jar jbossws-native-core.jar jbossws-common.jar"/>
+            jbossws-native-jaxws-ext.jar jbossws-native-core.jar jbossws-common.jar jboss-logging-spi.jar"/>
 
     <property name="com.arjuna.mwlabs.wsas.resourcebundle" value="wsas_msg_en_US.properties"/>
 

Modified: labs/jbosstm/trunk/XTS/WSCF/build.xml
===================================================================
--- labs/jbosstm/trunk/XTS/WSCF/build.xml	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WSCF/build.xml	2008-07-30 17:08:39 UTC (rev 21296)
@@ -79,7 +79,7 @@
     <property name="jta.ext.compile.jars" value="commons-logging-1.1.jar jta-1_1-classes.zip"/>
     <property name="jboss.client.lib.jars" value="jaxb-api.jar jboss-javaee.jar jbossws-native-jaxrpc.jar
             jbossws-native-jaxws.jar jbossws-client.jar stax-api.jar jbossws-native-saaj.jar
-            jbossws-native-jaxws-ext.jar jbossws-native-core.jar jbossws-common.jar"/>
+            jbossws-native-jaxws-ext.jar jbossws-native-core.jar jbossws-common.jar jboss-logging-spi.jar"/>
 
     <property name="wsas.libs" value="wsas.jar"/>
     <property name="wsc.libs" value="ws-c.jar"/>

Modified: labs/jbosstm/trunk/XTS/WSTX/build.xml
===================================================================
--- labs/jbosstm/trunk/XTS/WSTX/build.xml	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/WSTX/build.xml	2008-07-30 17:08:39 UTC (rev 21296)
@@ -77,7 +77,7 @@
     <property name="jta.ext.compile.jars" value="commons-logging.jar"/>
     <property name="jboss.client.lib.jars" value="jaxb-api.jar jboss-javaee.jar jbossws-native-jaxrpc.jar
             jbossws-native-jaxws.jar jbossws-client.jar stax-api.jar jbossws-native-saaj.jar
-            jbossws-native-jaxws-ext.jar jbossws-native-core.jar jbossws-common.jar"/>
+            jbossws-native-jaxws-ext.jar jbossws-native-core.jar jbossws-common.jar jboss-logging-spi.jar"/>
 
     <property name="wsas.libs" value="wsas.jar"/>
     <property name="wsc.libs" value="ws-c.jar"/>

Modified: labs/jbosstm/trunk/XTS/build.xml
===================================================================
--- labs/jbosstm/trunk/XTS/build.xml	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/build.xml	2008-07-30 17:08:39 UTC (rev 21296)
@@ -49,7 +49,7 @@
     <property name="com.arjuna.jta.install.ext.jars" value="jbossts-common.jar commons-logging-1.1.jar"/>
     <property name="jboss.client.lib.jars" value="jaxb-api.jar jboss-javaee.jar jbossws-native-jaxrpc.jar
             jbossws-native-jaxws.jar jbossws-client.jar stax-api.jar jbossws-native-saaj.jar
-            jbossws-native-jaxws-ext.jar jbossws-native-core.jar jbossws-common.jar"/>
+            jbossws-native-jaxws-ext.jar jbossws-native-core.jar jbossws-common.jar jboss-logging-spi.jar"/>
 
     <condition property="jboss.home" value="${env.JBOSS_HOME}">
         <isset property="env.JBOSS_HOME"/>

Modified: labs/jbosstm/trunk/XTS/demo/dd/jboss/service-web-app.xml
===================================================================
--- labs/jbosstm/trunk/XTS/demo/dd/jboss/service-web-app.xml	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/demo/dd/jboss/service-web-app.xml	2008-07-30 17:08:39 UTC (rev 21296)
@@ -24,7 +24,10 @@
 	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
 	version="2.4">
 
-	<servlet>
+    <listener>
+        <listener-class>com.jboss.jbosstm.xts.demo.services.recovery.DemoATRecoveryListener</listener-class>
+    </listener>
+    <servlet>
 		<servlet-name>RestaurantServiceAT</servlet-name>
 		<servlet-class>com.jboss.jbosstm.xts.demo.services.restaurant.RestaurantServiceAT</servlet-class>
 	</servlet>

Added: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/recovery/DemoATRecoveryListener.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/recovery/DemoATRecoveryListener.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/recovery/DemoATRecoveryListener.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -0,0 +1,21 @@
+package com.jboss.jbosstm.xts.demo.services.recovery;
+
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletContextEvent;
+
+/**
+ * Listener to register and unregister teh XTS application specific listener -- we have to
+ * use this because JBossWS does not currently honour the @PostConstruct and @PreDestroy
+ * lifecycle annotations on web services
+ */
+public class DemoATRecoveryListener implements ServletContextListener
+{
+
+    public void contextInitialized(ServletContextEvent event) {
+        DemoATRecoveryModule.register();
+    }
+
+    public void contextDestroyed(ServletContextEvent event) {
+        DemoATRecoveryModule.unregister();
+    }
+}

Added: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/recovery/DemoATRecoveryModule.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/recovery/DemoATRecoveryModule.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/recovery/DemoATRecoveryModule.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -0,0 +1,103 @@
+package com.jboss.jbosstm.xts.demo.services.recovery;
+
+import org.jboss.jbossts.xts.recovery.participant.at.XTSATRecoveryModule;
+import org.jboss.jbossts.xts.recovery.participant.at.XTSATRecoveryManager;
+import com.arjuna.wst.Durable2PCParticipant;
+
+import java.io.ObjectInputStream;
+
+/**
+ * Application-specific WS-AT participant recovery manager for demo application, This class
+ * is responsible for recreating application-specific durable participants from records
+ * logged at prepare time.
+ */
+public class DemoATRecoveryModule implements XTSATRecoveryModule
+{
+    /**
+     * the singleton recovery module
+     */
+    private static DemoATRecoveryModule theRecoveryModule = null;
+
+    /**
+     * a count of how many xts demo services are currently installed
+     */
+    private static int serviceCount = 0;
+
+    /**
+     * called during deployment of an xts-demo web service to ensure the recovery module for the
+     * demo is installed whenever any of the services is active
+     */
+    public static void register()
+    {
+        if (theRecoveryModule == null) {
+            theRecoveryModule = new DemoATRecoveryModule();
+        }
+        if (serviceCount == 0) {
+            XTSATRecoveryManager.getRecoveryManager().registerRecoveryModule(theRecoveryModule);
+        }
+        serviceCount++;
+    }
+
+    /**
+     * called during undeployment of an xts-demo web service to ensure the recovery module for
+     * the demo is deinstalled once none of the services is active
+     */
+    public static void unregister()
+    {
+        if (serviceCount > 0) {
+            serviceCount--;
+            if (serviceCount == 0) {
+                XTSATRecoveryManager.getRecoveryManager().unregisterRecoveryModule(theRecoveryModule);
+            }
+        }
+    }
+    
+    /**
+     * called during recovery processing to allow an application to identify a participant id
+     * belonging to one of its participants and recreate the participant by deserializing
+     * it from the supplied object input stream. n.b. this is only appropriate in case the
+     * participant was originally saved using serialization.
+     *
+     * @param id     the id used when the participant was created
+     * @param stream a stream from which the application should deserialise the participant
+     *               if it recognises that the id belongs to the module's application
+     * @return
+     * @throws Exception if an error occurs deserializing the durable participant
+     */
+    public Durable2PCParticipant deserialize(String id, ObjectInputStream stream) throws Exception {
+        if (id.startsWith("org.jboss.jbossts.xts-demo:restaurantAT") ||
+                id.startsWith("org.jboss.jbossts.xts-demo:theatreAT") ||
+                id.startsWith("org.jboss.jbossts.xts-demo:taxiAT")) {
+            System.out.println("xts-demo : attempting to deserialize WS-AT participant " + id);
+            Durable2PCParticipant participant = (Durable2PCParticipant)stream.readObject();
+            System.out.println("xts-demo : deserialized WS-AT participant " + id);
+            return participant;
+        }
+
+        return null;
+    }
+
+    /**
+     * called during recovery processing to allow an application to identify a participant id
+     * belonging to one of its participants and use the saved recovery state to recreate the
+     * participant. n.b. this is only appropriate in case the participant was originally saved
+     * after being converted to a byte array using the PersistibleATParticipant interface.
+     *
+     * @param id            the id used when the participant was created
+     * @param recoveryState a byte array returned form the original participant via a call to
+     *                      method getRecoveryState of interface PersistableATParticipant
+     * @return
+     * @throws Exception if an error occurs converting the recoveryState back to a
+     *                   durable participant
+     */
+    public Durable2PCParticipant recreate(String id, byte[] recoveryState) throws Exception {
+        if (id.startsWith("org.jboss.jbossts.xts-demo:restauarantAT") ||
+                id.startsWith("org.jboss.jbossts.xts-demo:theatreAT") ||
+                id.startsWith("org.jboss.jbossts.xts-demo:taxiAT")) {
+            // this should not get called -- xts-demo WS-AT participants are saved and restored
+            // using serialization
+            throw new Exception("xts-demo : invalid request to recreate() WS-AT participant " + id);
+        }
+        return null;
+    }
+}

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantManager.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantManager.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantManager.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -30,6 +30,8 @@
 package com.jboss.jbosstm.xts.demo.services.restaurant;
 
 import java.util.Hashtable;
+import java.util.Enumeration;
+import java.io.*;
 
 /**
  * The transactional application logic for the Restaurant Service.
@@ -37,17 +39,30 @@
  * Stores and manages seating reservations. Knows nothing about Web Services.
  * Understands transactional booking lifecycle: unprepared, prepared, finished.
  *
+ * </p>The manager maintains the following invariants regarding seating capacity:
+ * <ul>
+ * <li>nBooked == sum(unpreparedList.seatCount) + sum(preparedList.seatCount)
+ *
+ * <li>nPrepared = sum(prepared.seatCount)
+ *
+ * <li>nTotal == nFree + nPrepared + nCommitted
+ * </ul>
+ * changes to nPrepared, nFree, nCommitted, nTotal and preparedList are always shadowed in
+ * persistent storage before returning control to clients.
+ *
  * @author Jonathan Halliday (jonathan.halliday at arjuna.com)
  * @version $Revision: 1.3 $
  */
-public class RestaurantManager
+public class RestaurantManager implements Serializable
 {
     /**
      * Create and initialise a new RestaurantManager instance.
      */
-    public RestaurantManager()
+    private RestaurantManager()
     {
-        setToDefault();
+        setToDefault(false);
+        // restore any state saved by a previous installation of this web service
+        restoreState();
     }
 
     /**
@@ -56,7 +71,7 @@
      * @param txID   The transaction identifier
      * @param nSeats The number of seats requested
      */
-    public void bookSeats(Object txID, int nSeats)
+    public synchronized void bookSeats(Object txID, int nSeats)
     {
         // locate any pre-existing request for the same transaction
         Integer request = (Integer) unpreparedTransactions.get(txID);
@@ -73,6 +88,7 @@
 
         // record the increased commitment to provide seating
         nBookedSeats += nSeats;
+        // we don't actually need to update until prepare
     }
 
     /**
@@ -81,7 +97,7 @@
      * @param txID The transaction identifier
      * @return true on success, false otherwise
      */
-    public boolean prepareSeats(Object txID)
+    public synchronized boolean prepareSeats(Object txID)
     {
         // ensure that we have seen this transaction before
         Integer request = (Integer) unpreparedTransactions.get(txID);
@@ -101,6 +117,8 @@
                     // mark the prepared seats as unavailable
                     nFreeSeats -= request.intValue();
                     nPreparedSeats += request.intValue();
+                    updateState();
+
                     return true;
                 }
                 else
@@ -130,6 +148,7 @@
                         // mark the prepared seats as unavailable
                         nFreeSeats -= request.intValue();
                         nPreparedSeats += request.intValue();
+                        updateState();
                         return true;
                     }
                     else
@@ -152,7 +171,7 @@
      * @param txID The transaction identifier
      * @return true on success, false otherwise
      */
-    public boolean cancelSeats(Object txID)
+    public synchronized boolean cancelSeats(Object txID)
     {
         boolean success = false;
 
@@ -165,6 +184,7 @@
             nFreeSeats += request.intValue();
             nPreparedSeats -= request.intValue();
             nBookedSeats -= request.intValue();
+            updateState();
             success = true;
         }
         else if (unpreparedTransactions.containsKey(txID))
@@ -178,7 +198,6 @@
         {
             success = false; // error: transaction not registered
         }
-
         return success;
     }
 
@@ -188,7 +207,7 @@
      * @param txID The transaction identifier
      * @return true on success, false otherwise
      */
-    public boolean commitSeats(Object txID)
+    public synchronized boolean commitSeats(Object txID)
     {
         boolean success = false;
 
@@ -201,6 +220,7 @@
             nCommittedSeats += request.intValue();
             nPreparedSeats -= request.intValue();
             nBookedSeats -= request.intValue();
+            updateState();
             success = true;
         }
         else if (unpreparedTransactions.containsKey(txID))
@@ -210,6 +230,7 @@
             nCommittedSeats += request.intValue();
             nFreeSeats -= request.intValue();
             nBookedSeats -= request.intValue();
+            updateState();
             success = true;
         }
         else
@@ -354,10 +375,21 @@
     }
 
     /**
-     * (re-)initialise the instance data structures.
+     * (re-)initialise the instance data structures deleting any previously saved
+     * transaction state.
      */
     public void setToDefault()
     {
+        setToDefault(true);
+    }
+
+    /**
+     * (re-)initialise the instance data structures, potentially committing any saved state
+     * to disk
+     * @param deleteSavedState true if any cached transaction state should be deleted otherwise false
+     */
+    public void setToDefault(boolean deleteSavedState)
+    {
         nTotalSeats = DEFAULT_SEATING_CAPACITY;
         nFreeSeats = nTotalSeats;
         nBookedSeats = 0;
@@ -369,6 +401,10 @@
         preparation = new Object();
         isPreparationWaiting = false;
         isCommit = true;
+        if (deleteSavedState) {
+            // just write the current state.
+            updateState();
+        }
     }
 
     /**
@@ -376,7 +412,7 @@
      *
      * @return the singleton RestaurantManager instance.
      */
-    public static RestaurantManager getSingletonInstance()
+    public synchronized static RestaurantManager getSingletonInstance()
     {
         if (singletonInstance == null)
         {
@@ -419,7 +455,7 @@
 
     /**
      * The auto commit mode.
-     * <p/>
+     * <p/>                                 sa
      * true = automatically commit, false = manually commit
      */
     private boolean autoCommitMode;
@@ -453,4 +489,126 @@
      * The default initial capacity of each seating area.
      */
     public static final int DEFAULT_SEATING_CAPACITY = 100;
+
+    /**
+     * the name of the file sued to store the restaurant manager state
+     */
+    final static private String STATE_FILENAME = "restaurantManagerState";
+
+    /**
+     * the name of the file sued to store the restaurant manager shadow state
+     */
+    final static private String SHADOW_STATE_FILENAME = "restaurantManagerShadowState";
+
+    /**
+     * load any previously saved manager state
+     *
+     * n.b. can only be called once from the singleton constructor before save can be called
+     * so there is no need for any synchronization here
+     */
+
+    private void restoreState()
+    {
+        File file = new File(STATE_FILENAME);
+        File shadowFile = new File(SHADOW_STATE_FILENAME);
+        if (file.exists()) {
+            if (shadowFile.exists()) {
+                // crashed during shadow file write == just trash it
+                shadowFile.delete();
+            }
+        } else if (shadowFile.exists()) {
+            // crashed afetr successful write - promote shadow file to real file
+            shadowFile.renameTo(file);
+            file = new File(STATE_FILENAME);
+        }
+        if (file.exists()) {
+            try {
+                FileInputStream fis = new FileInputStream(file);
+                ObjectInputStream ois = new ObjectInputStream(fis);
+                readState(ois);
+            } catch (Exception e) {
+                System.out.println("error : could not restore restaurant manager state" + e);
+            }
+        } else {
+            System.out.println("Starting with default restaurant manager state");
+        }
+    }
+
+    /**
+     * write the current manager state to a shadow disk file then commit it as the latest state
+     * by relinking it to the current file
+     *
+     * n.b. must always called synchronized since the caller must always atomically check the
+     * current state, modify it and write it.
+     */
+    private void updateState()
+    {
+        File file = new File(STATE_FILENAME);
+        File shadowFile = new File(SHADOW_STATE_FILENAME);
+
+        if (shadowFile.exists()) {
+            // previous write must have barfed
+            shadowFile.delete();
+        }
+
+        try {
+            FileOutputStream fos = new FileOutputStream(shadowFile);
+            ObjectOutputStream oos = new ObjectOutputStream(fos);
+            writeState(oos);
+        } catch (Exception e) {
+            System.out.println("error : could not restore restaurant manager state" + e);
+        }
+
+        shadowFile.renameTo(file);
+    }
+
+    /**
+     * does the actual work of reading in the saved manager state
+     *
+     * @param ois
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    private void readState(ObjectInputStream ois) throws IOException, ClassNotFoundException
+    {
+        nTotalSeats = ois.readInt();
+        nFreeSeats = ois.readInt();
+        nPreparedSeats = ois.readInt();
+        nCommittedSeats = ois.readInt();
+        preparedTransactions = new Hashtable();
+        String name = (String)ois.readObject();
+        while (!"".equals(name)) {
+            int count = ois.readInt();
+            preparedTransactions.put(name, new Integer(count));
+            name = (String)ois.readObject();
+        }
+        unpreparedTransactions = new Hashtable();
+        // derive nBookedSeats from invariant
+        nBookedSeats = nPreparedSeats;
+        // assert invariant for total seats
+        assert nTotalSeats == nFreeSeats + nPreparedSeats + nCommittedSeats;
+    }
+
+    /**
+     * does the actual work of writing out the saved manager state
+     * @param oos
+     * @throws IOException
+     */
+    private void writeState(ObjectOutputStream oos) throws IOException
+    {
+        // assert invariant for total seats
+        assert nTotalSeats == nFreeSeats + nPreparedSeats + nCommittedSeats;
+        oos.writeInt(nTotalSeats);
+        oos.writeInt(nFreeSeats);
+        oos.writeInt(nPreparedSeats);
+        oos.writeInt(nCommittedSeats);
+        Enumeration keys = preparedTransactions.keys();
+        while (keys.hasMoreElements()) {
+            String name = (String)keys.nextElement();
+            int count = ((Integer)preparedTransactions.get(name)).intValue();
+            oos.writeObject(name);
+            oos.writeInt(count);
+        }
+        oos.writeObject("");
+    }
 }

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantParticipantAT.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantParticipantAT.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantParticipantAT.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -31,6 +31,9 @@
 
 import com.arjuna.wst.*;
 
+import java.io.Serializable;
+import java.io.IOException;
+
 /**
  * An adapter class that exposes the RestaurantManager transaction lifecycle
  * API as a WS-T Atomic Transaction participant.
@@ -39,7 +42,7 @@
  * @author Jonathan Halliday (jonathan.halliday at arjuna.com)
  * @version $Revision: 1.3 $
  */
-public class RestaurantParticipantAT implements Durable2PCParticipant
+public class RestaurantParticipantAT implements Durable2PCParticipant, Serializable
 {
     /**
      * Participant instances are related to transaction instances
@@ -49,9 +52,6 @@
      */
     public RestaurantParticipantAT(String txID)
     {
-        // Binds to the singleton RestaurantView and RestaurantManager
-        restaurantManager = RestaurantManager.getSingletonInstance();
-        restaurantView = RestaurantView.getSingletonInstance();
         // we need to save the txID for later use when calling
         // business logic methods in the restaurantManger.
         this.txID = txID;
@@ -72,24 +72,24 @@
 
         System.out.println("RestaurantParticipantAT.prepare");
 
-        restaurantView.addPrepareMessage("id:" + txID + ". Prepare called on participant: " + this.getClass().toString());
+        getRestaurantView().addPrepareMessage("id:" + txID + ". Prepare called on participant: " + this.getClass().toString());
 
-        boolean success = restaurantManager.prepareSeats(txID);
+        boolean success = getRestaurantManager().prepareSeats(txID);
 
         // Log the outcome and map the return value from
         // the business logic to the appropriate Vote type.
 
         if (success)
         {
-            restaurantView.addMessage("Seats prepared successfully. Returning 'Prepared'\n");
-            restaurantView.updateFields();
+            getRestaurantView().addMessage("Seats prepared successfully. Returning 'Prepared'\n");
+            getRestaurantView().updateFields();
             return new Prepared();
         }
         else
         {
-            restaurantManager.cancelSeats(txID) ;
-            restaurantView.addMessage("Prepare failed (not enough seats?) Returning 'Aborted'\n");
-            restaurantView.updateFields();
+            getRestaurantManager().cancelSeats(txID) ;
+            getRestaurantView().addMessage("Prepare failed (not enough seats?) Returning 'Aborted'\n");
+            getRestaurantView().updateFields();
             return new Aborted();
         }
     }
@@ -108,22 +108,22 @@
 
         System.out.println("RestaurantParticipantAT.commit");
 
-        restaurantView.addMessage("id:" + txID + ". Commit called on participant: " + this.getClass().toString());
+        getRestaurantView().addMessage("id:" + txID + ". Commit called on participant: " + this.getClass().toString());
 
-        boolean success = restaurantManager.commitSeats(txID);
+        boolean success = getRestaurantManager().commitSeats(txID);
 
         // Log the outcome
 
         if (success)
         {
-            restaurantView.addMessage("Seats committed\n");
+            getRestaurantView().addMessage("Seats committed\n");
         }
         else
         {
-            restaurantView.addMessage("Something went wrong (Transaction not registered?)\n");
+            getRestaurantView().addMessage("Something went wrong (Transaction not registered?)\n");
         }
 
-        restaurantView.updateFields();
+        getRestaurantView().updateFields();
     }
 
     /**
@@ -140,22 +140,22 @@
 
         System.out.println("RestaurantParticipantAT.rollback");
 
-        restaurantView.addMessage("id:" + txID + ". Rollback called on participant: " + this.getClass().toString());
+        getRestaurantView().addMessage("id:" + txID + ". Rollback called on participant: " + this.getClass().toString());
 
-        boolean success = restaurantManager.cancelSeats(txID);
+        boolean success = getRestaurantManager().cancelSeats(txID);
 
         // Log the outcome
 
         if (success)
         {
-            restaurantView.addMessage("Seats booking cancelled\n");
+            getRestaurantView().addMessage("Seats booking cancelled\n");
         }
         else
         {
-            restaurantView.addMessage("Something went wrong (Transaction not registered?)\n");
+            getRestaurantView().addMessage("Something went wrong (Transaction not registered?)\n");
         }
 
-        restaurantView.updateFields();
+        getRestaurantView().updateFields();
     }
 
     /**
@@ -188,14 +188,12 @@
      */
     protected String txID;
 
-    /**
-     * The RestaurantView object to log events through.
-     */
-    protected static RestaurantView restaurantView;
+    public RestaurantView getRestaurantView() {
+        return RestaurantView.getSingletonInstance();
+    }
 
-    /**
-     * The RestaurantManager to perform business logic operations on.
-     */
-    protected static RestaurantManager restaurantManager;
+    public RestaurantManager getRestaurantManager() {
+        return RestaurantManager.getSingletonInstance();
+    }
 }
 

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantServiceAT.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantServiceAT.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantServiceAT.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -33,13 +33,18 @@
 import com.arjuna.mw.wst11.TransactionManagerFactory;
 import com.arjuna.mw.wst11.UserTransactionFactory;
 import com.jboss.jbosstm.xts.demo.restaurant.IRestaurantServiceAT;
+import com.jboss.jbosstm.xts.demo.services.recovery.DemoATRecoveryModule;
 
 import javax.jws.HandlerChain;
 import javax.jws.WebParam;
 import javax.jws.WebService;
 import javax.jws.WebMethod;
 import javax.jws.soap.SOAPBinding;
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
 
+import org.jboss.jbossts.xts.recovery.participant.at.XTSATRecoveryManager;
+
 /**
  * An adapter class that exposes the RestaurantManager business API as a
  * transactional Web Service. Also logs events to a RestaurantView object.
@@ -55,6 +60,26 @@
 public class RestaurantServiceAT implements IRestaurantServiceAT
 {
     /**
+     * ensure that the recovery module for the dmeo is installed
+     */
+    @PostConstruct
+    void postConstruct()
+    {
+        // ensure that the xts-demo AT recovery helper module is registered
+        DemoATRecoveryModule.register();
+    }
+
+    /**
+     * ensure that the recovery module for the dmeo is deinstalled
+     */
+    @PreDestroy
+    void preDestroy()
+    {
+        // ensure that the xts-demo AT recovery helper module is registered
+        DemoATRecoveryModule.unregister();
+    }
+
+    /**
      * Book a number of seats in the restaurant
      * Enrols a Participant if necessary, then passes
      * the call through to the business logic.
@@ -81,7 +106,7 @@
                 System.out.println("RestaurantServiceAT - enrolling...");
                 // enlist the Participant for this service:
                 RestaurantParticipantAT restaurantParticipant = new RestaurantParticipantAT(transactionId);
-                TransactionManagerFactory.transactionManager().enlistForDurableTwoPhase(restaurantParticipant, new Uid().toString());
+                TransactionManagerFactory.transactionManager().enlistForDurableTwoPhase(restaurantParticipant, "org.jboss.jbossts.xts-demo:restaurantAT:" + new Uid().toString());
             }
         }
         catch (Exception e)

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantView.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantView.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantView.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -29,13 +29,15 @@
 
 package com.jboss.jbosstm.xts.demo.services.restaurant;
 
+import java.io.Serializable;
+
 /**
  * The visual interface (GUI) for the Restaurant Service.
  *
  * @author Jonathan Halliday (jonathan.halliday at arjuna.com)
  * @version $Revision: 1.2 $
  */
-public class RestaurantView extends javax.swing.JFrame
+public class RestaurantView extends javax.swing.JFrame implements Serializable
 {
     // Note: Some parts of this file were auto-generated
     // by NetBeans 3.3 FormEditor (http://www.netbeans.org)

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiManager.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiManager.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiManager.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -30,6 +30,8 @@
 package com.jboss.jbosstm.xts.demo.services.taxi;
 
 import java.util.Hashtable;
+import java.util.Enumeration;
+import java.io.*;
 
 /**
  * The transactional application logic for the Taxi Service
@@ -37,17 +39,21 @@
  * Stores and manages taxi reservations. Knows nothing about Web Services.
  * Understands transactional booking lifecycle: unprepared, prepared, finished.
  *
+ * </p>Tchanges to preparedList are always shadowed in persistent storage before
+ * returning control to clients.
+ *
  * @author Jonathan Halliday (jonathan.halliday at arjuna.com)
  * @version $Revision: 1.3 $
  */
-public class TaxiManager
+public class TaxiManager implements Serializable
 {
     /**
      * Create and initialise a new TaxiManager instance.
      */
-    public TaxiManager()
+    private TaxiManager()
     {
-        setToDefault();
+        setToDefault(false);
+        restoreState();
     }
 
     /**
@@ -55,7 +61,7 @@
      *
      * @param txID The transaction identifier
      */
-    public void bookTaxi(Object txID)
+    public synchronized void bookTaxi(Object txID)
     {
         // locate any pre-existing request for the same transaction
         Integer request = (Integer) unpreparedTransactions.get(txID);
@@ -68,6 +74,7 @@
 
         // record the request, keyed to its transaction scope
         unpreparedTransactions.put(txID, new Integer(request.intValue()));
+        // we don't actually need to update until prepare
     }
 
     /**
@@ -76,7 +83,7 @@
      * @param txID The transaction identifier
      * @return true on success, false otherwise
      */
-    public boolean prepareTaxi(Object txID)
+    public synchronized boolean prepareTaxi(Object txID)
     {
         // ensure that we have seen this transaction before
         Integer request = (Integer) unpreparedTransactions.get(txID);
@@ -91,6 +98,7 @@
                 // record the prepared transaction
                 preparedTransactions.put(txID, request);
                 unpreparedTransactions.remove(txID);
+                updateState();
                 return true;
             }
             else
@@ -109,6 +117,7 @@
                         // record the prepared transaction
                         preparedTransactions.put(txID, request);
                         unpreparedTransactions.remove(txID);
+                        updateState();
                         return true;
                     }
                     else
@@ -132,7 +141,7 @@
      * @param txID The transaction identifier
      * @return true on success, false otherwise
      */
-    public boolean cancelTaxi(Object txID)
+    public synchronized boolean cancelTaxi(Object txID)
     {
         boolean success = false;
 
@@ -142,12 +151,14 @@
         {
             // undo the prepare operations
             preparedTransactions.remove(txID);
+            updateState();
             success = true;
         }
         else if (unpreparedTransactions.containsKey(txID))
         {
             // undo the booking operations
             unpreparedTransactions.remove(txID);
+            // we don't need to update state
             success = true;
         }
         else
@@ -164,7 +175,7 @@
      * @param txID The transaction identifier
      * @return true on success, false otherwise
      */
-    public boolean commitTaxi(Object txID)
+    public synchronized boolean commitTaxi(Object txID)
     {
         boolean success = false;
         hasCommitted = true;
@@ -175,12 +186,14 @@
         {
             // complete the prepared transaction
             preparedTransactions.remove(txID);
+            updateState();
             success = true;
         }
         else if (unpreparedTransactions.containsKey(txID))
         {
             // use one phase commit optimisation, skipping prepare
             unpreparedTransactions.remove(txID);
+            // we don't need to update state
             success = true;
         }
         else
@@ -262,10 +275,21 @@
     }
 
     /**
-     * (re-)initialise the instance data structures.
+     * (re-)initialise the instance data structures deleting any previously saved
+     * transaction state.
      */
     public void setToDefault()
     {
+        setToDefault(true);
+    }
+
+    /**
+     * (re-)initialise the instance data structures, potentially committing any saved state
+     * to disk
+     * @param deleteSavedState true if any cached transaction state should be deleted otherwise false
+     */
+    public void setToDefault(boolean deleteSavedState)
+    {
         preparedTransactions = new Hashtable();
         unpreparedTransactions = new Hashtable();
         autoCommitMode = true;
@@ -273,12 +297,16 @@
         isPreparationWaiting = false;
         isCommit = false;
         hasCommitted = false;
+        if (deleteSavedState) {
+            // just write the current state.
+            updateState();
+        }
     }
 
     /**
      * Allow use of a singleton model for web services demo.
      */
-    public static TaxiManager getSingletonInstance()
+    public synchronized static TaxiManager getSingletonInstance()
     {
         if (singletonInstance == null)
         {
@@ -344,4 +372,112 @@
      * The waiting status, when in manual commit mode.
      */
     private boolean isPreparationWaiting;
+
+    /**
+     * the name of the file sued to store the restaurant manager state
+     */
+    final static private String STATE_FILENAME = "taxiManagerState";
+
+    /**
+     * the name of the file sued to store the restaurant manager shadow state
+     */
+    final static private String SHADOW_STATE_FILENAME = "taxiManagerShadowState";
+
+    /**
+     * load any previously saved manager state.
+     *
+     * n.b. can only be called once from the singleton constructor before save can be called
+     * so there is no need for any synchronization here
+     */
+
+    private synchronized void restoreState()
+    {
+        File file = new File(STATE_FILENAME);
+        File shadowFile = new File(SHADOW_STATE_FILENAME);
+        if (file.exists()) {
+            if (shadowFile.exists()) {
+                // crashed during shadow file write == just trash it
+                shadowFile.delete();
+            }
+        } else if (shadowFile.exists()) {
+            // crashed afetr successful write - promote shadow file to real file
+            shadowFile.renameTo(file);
+            file = new File(STATE_FILENAME);
+        }
+        if (file.exists()) {
+            try {
+                FileInputStream fis = new FileInputStream(file);
+                ObjectInputStream ois = new ObjectInputStream(fis);
+                readState(ois);
+            } catch (Exception e) {
+                System.out.println("error : could not restore restaurant manager state" + e);
+            }
+        } else {
+            System.out.println("Starting with default restaurant manager state");
+        }
+    }
+
+    /**
+     * write the current manager state to a shadow disk file then commit it as the latest state
+     * by relinking it to the current file
+     *
+     * n.b. must always called synchronized since the caller must always atomically check the
+     * current state, modify it and write it.
+     */
+    private void updateState()
+    {
+        File file = new File(STATE_FILENAME);
+        File shadowFile = new File(SHADOW_STATE_FILENAME);
+
+        if (shadowFile.exists()) {
+            // previous write must have barfed
+            shadowFile.delete();
+        }
+
+        try {
+            FileOutputStream fos = new FileOutputStream(shadowFile);
+            ObjectOutputStream oos = new ObjectOutputStream(fos);
+            writeState(oos);
+        } catch (Exception e) {
+            System.out.println("error : could not restore restaurant manager state" + e);
+        }
+
+        shadowFile.renameTo(file);
+    }
+
+    /**
+     * does the actual work of reading in the saved manager state
+     *
+     * @param ois
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    private void readState(ObjectInputStream ois) throws IOException, ClassNotFoundException
+    {
+        preparedTransactions = new Hashtable();
+        String name = (String)ois.readObject();
+        while (!"".equals(name)) {
+            int count = ois.readInt();
+            preparedTransactions.put(name, new Integer(count));
+            name = (String)ois.readObject();
+        }
+        unpreparedTransactions = new Hashtable();
+    }
+
+    /**
+     * does the actual work of writing out the saved manager state
+     * @param oos
+     * @throws IOException
+     */
+    private void writeState(ObjectOutputStream oos) throws IOException
+    {
+        Enumeration keys = preparedTransactions.keys();
+        while (keys.hasMoreElements()) {
+            String name = (String)keys.nextElement();
+            int count = ((Integer)preparedTransactions.get(name)).intValue();
+            oos.writeObject(name);
+            oos.writeInt(count);
+        }
+        oos.writeObject("");
+    }
 }

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiParticipantAT.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiParticipantAT.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiParticipantAT.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -31,6 +31,8 @@
 
 import com.arjuna.wst.*;
 
+import java.io.Serializable;
+
 /**
  * An adapter class that exposes the TaxiManager transaction lifecycle
  * API as a WS-T Atomic Transaction participant.
@@ -39,7 +41,7 @@
  * @author Jonathan Halliday (jonathan.halliday at arjuna.com)
  * @version $Revision: 1.3 $
  */
-public class TaxiParticipantAT implements Durable2PCParticipant
+public class TaxiParticipantAT implements Durable2PCParticipant, Serializable
 {
     /**
      * Participant instances are related to transaction instances
@@ -49,9 +51,6 @@
      */
     public TaxiParticipantAT(String txID)
     {
-        // Binds to the singleton TaxiView and TaxiManager
-        taxiManager = TaxiManager.getSingletonInstance();
-        taxiView = TaxiView.getSingletonInstance();
         // we need to save the txID for later use when calling
         // business logic methods in the taxiManger.
         this.txID = txID;
@@ -72,24 +71,24 @@
 
         System.out.println("TaxiParticipantAT.prepare");
 
-        taxiView.addPrepareMessage("id:" + txID + ". Prepare called on participant: " + this.getClass().toString());
+        getTaxiView().addPrepareMessage("id:" + txID + ". Prepare called on participant: " + this.getClass().toString());
 
-        boolean success = taxiManager.prepareTaxi(txID);
+        boolean success = getTaxiManager().prepareTaxi(txID);
 
         // Log the outcome and map the return value from
         // the business logic to the appropriate Vote type.
 
         if (success)
         {
-            taxiView.addMessage("Taxi prepared successfully. Returning 'Prepared'\n");
-            taxiView.updateFields();
+            getTaxiView().addMessage("Taxi prepared successfully. Returning 'Prepared'\n");
+            getTaxiView().updateFields();
             return new Prepared();
         }
         else
         {
-            taxiManager.cancelTaxi(txID) ;
-            taxiView.addMessage("Prepare failed (not enough Taxis?) Returning 'Aborted'\n");
-            taxiView.updateFields();
+            getTaxiManager().cancelTaxi(txID) ;
+            getTaxiView().addMessage("Prepare failed (not enough Taxis?) Returning 'Aborted'\n");
+            getTaxiView().updateFields();
             return new Aborted();
         }
     }
@@ -108,22 +107,22 @@
 
         System.out.println("TaxiParticipantAT.commit");
 
-        taxiView.addMessage("id:" + txID + ". Commit called on participant: " + this.getClass().toString());
+        getTaxiView().addMessage("id:" + txID + ". Commit called on participant: " + this.getClass().toString());
 
-        boolean success = taxiManager.commitTaxi(txID);
+        boolean success = getTaxiManager().commitTaxi(txID);
 
         // Log the outcome
 
         if (success)
         {
-            taxiView.addMessage("Taxi committed\n");
+            getTaxiView().addMessage("Taxi committed\n");
         }
         else
         {
-            taxiView.addMessage("Something went wrong (Transaction not registered?)\n");
+            getTaxiView().addMessage("Something went wrong (Transaction not registered?)\n");
         }
 
-        taxiView.updateFields();
+        getTaxiView().updateFields();
     }
 
     /**
@@ -140,22 +139,22 @@
 
         System.out.println("TaxiParticipantAT.rollback");
 
-        taxiView.addMessage("id:" + txID + ". Rollback called on participant: " + this.getClass().toString());
+        getTaxiView().addMessage("id:" + txID + ". Rollback called on participant: " + this.getClass().toString());
 
-        boolean success = taxiManager.cancelTaxi(txID);
+        boolean success = getTaxiManager().cancelTaxi(txID);
 
         // Log the outcome
 
         if (success)
         {
-            taxiView.addMessage("Taxi booking cancelled\n");
+            getTaxiView().addMessage("Taxi booking cancelled\n");
         }
         else
         {
-            taxiView.addMessage("Something went wrong (Transaction not registered?)\n");
+            getTaxiView().addMessage("Something went wrong (Transaction not registered?)\n");
         }
 
-        taxiView.updateFields();
+        getTaxiView().updateFields();
     }
 
     /**
@@ -188,13 +187,11 @@
      */
     protected String txID;
 
-    /**
-     * The TaxiView object to log events through.
-     */
-    protected static TaxiView taxiView;
+    public TaxiView getTaxiView() {
+        return TaxiView.getSingletonInstance();
+    }
 
-    /**
-     * The TaxiManager to perform business logic operations on.
-     */
-    protected static TaxiManager taxiManager;
+    public TaxiManager getTaxiManager() {
+        return TaxiManager.getSingletonInstance();
+    }
 }

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiServiceAT.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiServiceAT.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiServiceAT.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -33,11 +33,14 @@
 import com.arjuna.mw.wst11.TransactionManagerFactory;
 import com.arjuna.mw.wst11.UserTransactionFactory;
 import com.jboss.jbosstm.xts.demo.taxi.ITaxiServiceAT;
+import com.jboss.jbosstm.xts.demo.services.recovery.DemoATRecoveryModule;
 
 import javax.jws.WebService;
 import javax.jws.HandlerChain;
 import javax.jws.WebMethod;
 import javax.jws.soap.SOAPBinding;
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
 
 /**
  * An adapter class that exposes the TaxiManager business API as a
@@ -54,6 +57,26 @@
 public class TaxiServiceAT implements ITaxiServiceAT
 {
     /**
+     * ensure that the recovery module for the dmeo is installed
+     */
+    @PostConstruct
+    void postConstruct()
+    {
+        // ensure that the xts-demo AT recovery helper module is registered
+        DemoATRecoveryModule.register();
+    }
+
+    /**
+     * ensure that the recovery module for the dmeo is deinstalled
+     */
+    @PreDestroy
+    void preDestroy()
+    {
+        // ensure that the xts-demo AT recovery helper module is registered
+        DemoATRecoveryModule.unregister();
+    }
+
+    /**
      * Book a taxi
      * Enrols a Participant if necessary, then passes
      * the call through to the business logic.
@@ -76,7 +99,7 @@
                 System.out.println("TaxiServiceAT - enrolling...");
                 // enlist the Participant for this service:
                 TaxiParticipantAT taxiParticipant = new TaxiParticipantAT(transactionId);
-                TransactionManagerFactory.transactionManager().enlistForDurableTwoPhase(taxiParticipant, new Uid().toString());
+                TransactionManagerFactory.transactionManager().enlistForDurableTwoPhase(taxiParticipant, "org.jboss.jbossts.xts-demo:taxiAT:" + new Uid().toString());
             }
         }
         catch (Exception e)

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiView.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiView.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiView.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -29,13 +29,15 @@
 
 package com.jboss.jbosstm.xts.demo.services.taxi;
 
+import java.io.Serializable;
+
 /**
  * The visual interface (GUI) for the Taxi Service.
  *
  * @author Jonathan Halliday (jonathan.halliday at arjuna.com)
  * @version $Revision: 1.2 $
  */
-public class TaxiView extends javax.swing.JFrame
+public class TaxiView extends javax.swing.JFrame implements Serializable
 {
     // Note: Some parts of this file were auto-generated
     // by NetBeans 3.3 FormEditor (http://www.netbeans.org)

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreManager.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreManager.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreManager.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -30,6 +30,8 @@
 package com.jboss.jbosstm.xts.demo.services.theatre;
 
 import java.util.Hashtable;
+import java.util.Enumeration;
+import java.io.*;
 
 /**
  * The transactional application logic for the Theatre Service.
@@ -37,17 +39,29 @@
  * Stores and manages seating reservations. Knows nothing about Web Services.
  * Understands transactional booking lifecycle: unprepared, prepared, finished.
  *
+ * </p>The manager maintains the following invariants regarding seating capacity:
+ * <ul>
+ * <li>nBooked[area] == sum(unpreparedList.seatCount[area]) + sum(preparedList.seatCount[area])
+ *
+ * <li>nPrepared[area] = sum(prepared.seatCount[area])
+ *
+ * <li>nTotal[area] == nFree[area] + nPrepared[area] + nCommitted[area]
+ * </ul>
+ * changes to nPrepared, nFree, nCommitted, nTotal and preparedList are always shadowed in
+ * persistent storage before returning control to clients.
+ *
  * @author Jonathan Halliday (jonathan.halliday at arjuna.com)
  * @version $Revision: 1.4 $
  */
-public class TheatreManager
+public class TheatreManager implements Serializable
 {
     /**
      * Create and initialise a new TheatreManager instance.
      */
     public TheatreManager()
     {
-        setToDefault();
+        setToDefault(false);
+        restoreState();
     }
 
     /**
@@ -57,7 +71,7 @@
      * @param nSeats The number of seats requested
      * @param area   The type of seating requested
      */
-    public void bookSeats(Object txID, int nSeats, int area)
+    public synchronized void bookSeats(Object txID, int nSeats, int area)
     {
         // locate any pre-existing request for the same transaction
         Integer[] requests = (Integer[]) unpreparedTransactions.get(txID);
@@ -78,6 +92,7 @@
 
         // record the increased commitment to provide seating
         nBookedSeats[area] += nSeats;
+        // we don't actually need to update until prepare
     }
 
     /**
@@ -86,7 +101,7 @@
      * @param txID The transaction identifier
      * @return true on success, false otherwise
      */
-    public boolean prepareSeats(Object txID)
+    public synchronized boolean prepareSeats(Object txID)
     {
         int[] nSeats = new int[NUM_SEAT_AREAS];
 
@@ -126,6 +141,7 @@
                         nFreeSeats[i] = nSeats[i];
                         nPreparedSeats[i] += requests[i].intValue();
                     }
+                    updateState();
                 }
                 return success;
             }
@@ -153,6 +169,7 @@
                             nFreeSeats[i] = nSeats[i];
                             nPreparedSeats[i] += requests[i].intValue();
                         }
+                        updateState();
                         return true;
                     }
                     else
@@ -191,6 +208,7 @@
                 nPreparedSeats[i] -= requests[i].intValue();
                 nBookedSeats[i] -= requests[i].intValue();
             }
+            updateState();
             success = true;
         }
         else if (unpreparedTransactions.containsKey(txID))
@@ -201,6 +219,7 @@
             {
                 nBookedSeats[i] -= requests[i].intValue();
             }
+            // we don't need to update state
             success = true;
         }
         else
@@ -234,6 +253,7 @@
                 nPreparedSeats[i] -= requests[i].intValue();
                 nBookedSeats[i] -= requests[i].intValue();
             }
+            updateState();
             success = true;
         }
         else if (unpreparedTransactions.containsKey(txID))
@@ -246,6 +266,7 @@
                 nFreeSeats[i] -= requests[i].intValue();
                 nBookedSeats[i] -= requests[i].intValue();
             }
+            updateState();
             success = true;
         }
         else
@@ -394,10 +415,20 @@
     }
 
     /**
-     * (re-)initialise the instance data structures.
+     * (re-)initialise the instance data structures deleting any previously saved
+     * transaction state.
      */
     public void setToDefault()
     {
+        setToDefault(true);
+    }
+    /**
+     * (re-)initialise the instance data structures, potentially committing any saved state
+     * to disk
+     * @param deleteSavedState true if any cached transaction state should be deleted otherwise false
+     */
+    public void setToDefault(boolean deleteSavedState)
+    {
         nTotalSeats = new int[NUM_SEAT_AREAS];
         nFreeSeats = new int[NUM_SEAT_AREAS];
         nBookedSeats = new int[NUM_SEAT_AREAS];
@@ -417,12 +448,16 @@
         preparation = new Object();
         isPreparationWaiting = false;
         isCommit = true;
+        if (deleteSavedState) {
+            // just write the current state.
+            updateState();
+        }
     }
 
     /**
      * Allow use of a singleton model for web services demo.
      */
-    public static TheatreManager getSingletonInstance()
+    public synchronized static TheatreManager getSingletonInstance()
     {
         if (singletonInstance == null)
         {
@@ -526,4 +561,138 @@
      * The default initial capacity of each seating area.
      */
     public static final int DEFAULT_SEATING_CAPACITY = 100;
+
+    /**
+     * the name of the file sued to store the restaurant manager state
+     */
+    final static private String STATE_FILENAME = "theatreManagerState";
+
+    /**
+     * the name of the file sued to store the restaurant manager shadow state
+     */
+    final static private String SHADOW_STATE_FILENAME = "theatreManagerShadowState";
+
+    /**
+     * load any previously saved manager state
+     *
+     * n.b. can only be called once from the singleton constructor before save can be called
+     * so there is no need for any synchronization here
+     */
+
+    private void restoreState()
+    {
+        File file = new File(STATE_FILENAME);
+        File shadowFile = new File(SHADOW_STATE_FILENAME);
+        if (file.exists()) {
+            if (shadowFile.exists()) {
+                // crashed during shadow file write == just trash it
+                shadowFile.delete();
+            }
+        } else if (shadowFile.exists()) {
+            // crashed afetr successful write - promote shadow file to real file
+            shadowFile.renameTo(file);
+            file = new File(STATE_FILENAME);
+        }
+        if (file.exists()) {
+            try {
+                FileInputStream fis = new FileInputStream(file);
+                ObjectInputStream ois = new ObjectInputStream(fis);
+                readState(ois);
+            } catch (Exception e) {
+                System.out.println("error : could not restore restaurant manager state" + e);
+            }
+        } else {
+            System.out.println("Starting with default restaurant manager state");
+        }
+    }
+
+    /**
+     * write the current manager state to a shadow disk file then commit it as the latest state
+     * by relinking it to the current file
+     *
+     * n.b. must always called synchronized since the caller must always atomically check the
+     * current state, modify it and write it.
+     */
+    private void updateState()
+    {
+        File file = new File(STATE_FILENAME);
+        File shadowFile = new File(SHADOW_STATE_FILENAME);
+
+        if (shadowFile.exists()) {
+            // previous write must have barfed
+            shadowFile.delete();
+        }
+
+        try {
+            FileOutputStream fos = new FileOutputStream(shadowFile);
+            ObjectOutputStream oos = new ObjectOutputStream(fos);
+            writeState(oos);
+        } catch (Exception e) {
+            System.out.println("error : could not restore restaurant manager state" + e);
+        }
+
+        shadowFile.renameTo(file);
+    }
+
+    /**
+     * does the actual work of reading in the saved manager state
+     *
+     * @param ois
+     * @throws IOException
+     * @throws ClassNotFoundException
+     */
+    private void readState(ObjectInputStream ois) throws IOException, ClassNotFoundException
+    {
+        for (int i = 0; i < NUM_SEAT_AREAS; i++) {
+            nTotalSeats[i] = ois.readInt();
+            nFreeSeats[i] = ois.readInt();
+            nPreparedSeats[i] = ois.readInt();
+            nCommittedSeats[i] = ois.readInt();
+        }
+        preparedTransactions = new Hashtable();
+        String name = (String)ois.readObject();
+        while (!"".equals(name)) {
+            Integer[] counts = new Integer[NUM_SEAT_AREAS];
+            for (int i = 0; i < NUM_SEAT_AREAS; i++) {
+                int count = ois.readInt();
+                counts[i] = new Integer(count);
+            }
+            preparedTransactions.put(name, counts);
+            name = (String)ois.readObject();
+        }
+        unpreparedTransactions = new Hashtable();
+        for (int i = 0; i < NUM_SEAT_AREAS; i++) {
+            // derive nBookedSeats from invariant
+            nBookedSeats[i] = nPreparedSeats[i];
+            // assert invariant for total seats
+            assert nTotalSeats[i] == nFreeSeats[i] + nPreparedSeats[i] + nCommittedSeats[i];
+        }
+    }
+
+    /**
+     * does the actual work of writing out the saved manager state
+     * @param oos
+     * @throws IOException
+     */
+    private void writeState(ObjectOutputStream oos) throws IOException
+    {
+        for (int i = 0; i < NUM_SEAT_AREAS; i++) {
+            // assert invariant for total seats
+            assert nTotalSeats[i] == nFreeSeats[i] + nPreparedSeats[i] + nCommittedSeats[i];
+            oos.writeInt(nTotalSeats[i]);
+            oos.writeInt(nFreeSeats[i]);
+            oos.writeInt(nPreparedSeats[i]);
+            oos.writeInt(nCommittedSeats[i]);
+        }
+        Enumeration keys = preparedTransactions.keys();
+        while (keys.hasMoreElements()) {
+            String name = (String)keys.nextElement();
+            Integer[] counts = (Integer[]) preparedTransactions.get(name);
+            oos.writeObject(name);
+            for (int i = 0; i < NUM_SEAT_AREAS; i++) {
+                oos.writeInt(counts[i].intValue());
+            }
+        }
+        oos.writeObject("");
+    }
 }

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreParticipantAT.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreParticipantAT.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreParticipantAT.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -31,6 +31,8 @@
 
 import com.arjuna.wst.*;
 
+import java.io.Serializable;
+
 /**
  * An adapter class that exposes the TheatreManager transaction lifecycle
  * API as a WS-T Atomic Transaction participant.
@@ -39,7 +41,7 @@
  * @author Jonathan Halliday (jonathan.halliday at arjuna.com)
  * @version $Revision: 1.3 $
  */
-public class TheatreParticipantAT implements Durable2PCParticipant
+public class TheatreParticipantAT implements Durable2PCParticipant, Serializable
 {
     /**
      * Participant instances are related to transaction instances
@@ -49,9 +51,6 @@
      */
     public TheatreParticipantAT(String txID)
     {
-        // Binds to the singleton TheatreView and TheatreManager
-        theatreManager = TheatreManager.getSingletonInstance();
-        theatreView = TheatreView.getSingletonInstance();
         // we need to save the txID for later use when calling
         // business logic methods in the theatreManger.
         this.txID = txID;
@@ -72,9 +71,9 @@
 
         System.out.println("TheatreParticipantAT.prepare");
 
-        theatreView.addPrepareMessage("id:" + txID + ". Prepare called on participant: " + this.getClass().toString());
+        getTheatreView().addPrepareMessage("id:" + txID + ". Prepare called on participant: " + this.getClass().toString());
 
-        boolean success = theatreManager.prepareSeats(txID);
+        boolean success = getTheatreManager().prepareSeats(txID);
 
         // Log the outcome and map the return value from
         // the business logic to the appropriate Vote type.
@@ -82,15 +81,15 @@
 
         if (success)
         {
-            theatreView.addMessage("Theatre prepared successfully. Returning 'Prepared'\n");
-            theatreView.updateFields();
+            getTheatreView().addMessage("Theatre prepared successfully. Returning 'Prepared'\n");
+            getTheatreView().updateFields();
             return new Prepared();
         }
         else
         {
-            theatreManager.cancelSeats(txID) ;
-            theatreView.addMessage("Prepare failed (not enough seats?) Returning 'Aborted'\n");
-            theatreView.updateFields();
+            getTheatreManager().cancelSeats(txID) ;
+            getTheatreView().addMessage("Prepare failed (not enough seats?) Returning 'Aborted'\n");
+            getTheatreView().updateFields();
             return new Aborted();
         }
     }
@@ -109,22 +108,22 @@
 
         System.out.println("TheatreParticipantAT.commit");
 
-        theatreView.addMessage("id:" + txID + ". Commit called on participant: " + this.getClass().toString());
+        getTheatreView().addMessage("id:" + txID + ". Commit called on participant: " + this.getClass().toString());
 
-        boolean success = theatreManager.commitSeats(txID);
+        boolean success = getTheatreManager().commitSeats(txID);
 
         // Log the outcome
 
         if (success)
         {
-            theatreView.addMessage("Theatre tickets committed\n");
+            getTheatreView().addMessage("Theatre tickets committed\n");
         }
         else
         {
-            theatreView.addMessage("Something went wrong (Transaction not registered?)\n");
+            getTheatreView().addMessage("Something went wrong (Transaction not registered?)\n");
         }
 
-        theatreView.updateFields();
+        getTheatreView().updateFields();
     }
 
     /**
@@ -141,22 +140,22 @@
 
         System.out.println("TheatreParticipantAT.rollback");
 
-        theatreView.addMessage("id:" + txID + ". Rollback called on participant: " + this.getClass().toString());
+        getTheatreView().addMessage("id:" + txID + ". Rollback called on participant: " + this.getClass().toString());
 
-        boolean success = theatreManager.cancelSeats(txID);
+        boolean success = getTheatreManager().cancelSeats(txID);
 
         // Log the outcome
 
         if (success)
         {
-            theatreView.addMessage("Theatre booking cancelled\n");
+            getTheatreView().addMessage("Theatre booking cancelled\n");
         }
         else
         {
-            theatreView.addMessage("Something went wrong (Transaction not registered?)\n");
+            getTheatreView().addMessage("Something went wrong (Transaction not registered?)\n");
         }
 
-        theatreView.updateFields();
+        getTheatreView().updateFields();
     }
 
     /**
@@ -189,13 +188,11 @@
      */
     protected String txID;
 
-    /**
-     * The TheatreView object to log events through.
-     */
-    protected static TheatreView theatreView;
+    public TheatreView getTheatreView() {
+        return TheatreView.getSingletonInstance();
+    }
 
-    /**
-     * The TheatreManager to perform business logic operations on.
-     */
-    protected static TheatreManager theatreManager;
+    public TheatreManager getTheatreManager() {
+        return TheatreManager.getSingletonInstance();
+    }
 }

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreServiceAT.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreServiceAT.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreServiceAT.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -33,12 +33,15 @@
 import com.arjuna.mw.wst11.TransactionManagerFactory;
 import com.arjuna.mw.wst11.UserTransactionFactory;
 import com.jboss.jbosstm.xts.demo.theatre.ITheatreServiceAT;
+import com.jboss.jbosstm.xts.demo.services.recovery.DemoATRecoveryModule;
 
 import javax.jws.WebService;
 import javax.jws.WebParam;
 import javax.jws.HandlerChain;
 import javax.jws.WebMethod;
 import javax.jws.soap.SOAPBinding;
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
 
 /**
  * An adapter class that exposes the TheatreManager business API as a
@@ -55,6 +58,26 @@
 public class TheatreServiceAT implements ITheatreServiceAT
 {
     /**
+     * ensure that the recovery module for the dmeo is installed
+     */
+    @PostConstruct
+    void postConstruct()
+    {
+        // ensure that the xts-demo AT recovery helper module is registered
+        DemoATRecoveryModule.register();
+    }
+
+    /**
+     * ensure that the recovery module for the dmeo is deinstalled
+     */
+    @PreDestroy
+    void preDestroy()
+    {
+        // ensure that the xts-demo AT recovery helper module is registered
+        DemoATRecoveryModule.unregister();
+    }
+
+    /**
      * Book a number of seats in the Theatre
      * Enrols a Participant if necessary, then passes
      * the call through to the business logic.
@@ -84,7 +107,7 @@
                 System.out.println("theatreService - enrolling...");
                 // enlist the Participant for this service:
                 TheatreParticipantAT theatreParticipant = new TheatreParticipantAT(transactionId);
-                TransactionManagerFactory.transactionManager().enlistForDurableTwoPhase(theatreParticipant, new Uid().toString());
+                TransactionManagerFactory.transactionManager().enlistForDurableTwoPhase(theatreParticipant, "org.jboss.jbossts.xts-demo:theatreAT:" + new Uid().toString());
             }
         }
         catch (Exception e)

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreView.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreView.java	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreView.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -29,13 +29,15 @@
 
 package com.jboss.jbosstm.xts.demo.services.theatre;
 
+import java.io.Serializable;
+
 /**
  * The visual interface (GUI) for the Theatre Service.
  *
  * @author Jonathan Halliday (jonathan.halliday at arjuna.com)
  * @version $Revision: 1.3 $
  */
-public class TheatreView extends javax.swing.JFrame
+public class TheatreView extends javax.swing.JFrame implements Serializable
 {
 
     // Note: Some parts of this file were auto-generated

Added: labs/jbosstm/trunk/XTS/docs/XTSRecoveryNotes.txt
===================================================================
--- labs/jbosstm/trunk/XTS/docs/XTSRecoveryNotes.txt	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/docs/XTSRecoveryNotes.txt	2008-07-30 17:08:39 UTC (rev 21296)
@@ -0,0 +1,416 @@
+           WS-AT Coordinator/Participant Recovery Overview
+           -----------------------------------------------
+
+WS-AT Participant Side
+----------------------
+
+WS-AT has a generic participant interface
+
+interface Participant
+{
+    public Vote prepare () throws WrongStateException, SystemException;
+    public void commit () throws WrongStateException, SystemException;
+    public void rollback () throws WrongStateException, SystemException;
+    public void unknown () throws SystemException;
+    void error () throws SystemException;
+}
+
+Participant has two main subinterfaces which are used when registering
+either a volatile or a durable participant.
+
+public interface Volatile2PCParticipant extends Participant
+{
+}
+
+and
+
+public interface Durable2PCParticipant extends Participant
+{
+}
+
+Each of these three interfaces is employed by both the 1.0 and 1.1
+implementations. They are all located in package com.arjuna.wst.
+
+An application which wishes to employ WS-AT to manage transactional
+resources must define classes implementing these interfaces which
+manage the application's transactional state. It can then register
+instances of these classes as participants using the enlistment APIs
+associated with the WS-AT transaction manager class.
+
+Volatile participants are not expected to be involved in
+commit/rollback or recovery processing (even though they implement
+commit(), rollback() and unknown() these should never be called). They
+are expected to perform any necessary saving or flushing of volatile
+state at prepare and, if necessary, delete any redundant in-memory or
+persistent state at the same time. They must vote against prepare if
+their volatile state cannot be saved/flushed.
+
+Durable participants are expected to persist all necessary
+transactional state before or at prepare and it is at this latter
+point that they need to persist any recovery state i.e. information
+which may be needed to either roll forward or roll back prepared
+transactional state. Durable participants must perform roll forward or
+roll back of transactional state at commit/rollback and it is at this
+point that they are expected to delete any saved recovery state.
+During crash recovery processing the recovery state for a participant
+will be located and used to recreate the participant allowing a
+subsequent roll forward or roll back of the transactional state to be
+performed.
+
+Details of the API provided to allow recovery state to be identified
+and restored during recovery processing are provided below.
+
+WS-AT Coordinator Side
+----------------------
+
+On the XTS coordinator side information about participants is retained
+using stubs. These stub participants shadow the state of the actual
+participants, recording and validating state changes notified via
+incoming messages.
+
+public class ParticipantStub implements Participant, PersistableParticipant
+{
+    public Vote prepare() throws WrongStateException, SystemException;
+    public void commit() throws WrongStateException, SystemException;
+    public void rollback() throws WrongStateException, SystemException;
+    public void unknown() throws SystemException;
+    public void error() throws SystemException;
+    public boolean saveState(final OutputObjectState oos);
+    public boolean restoreState(final InputObjectState ios);
+}
+
+ParticipantStub is specialised by two subclasses, Durable2PCStub and
+Volatile2PCStub. 
+
+public class Durable2PCStub extends ParticipantStub
+    implements Durable2PCParticipant
+{
+    Durable2PCStub(String id, W3CEndpointReference twoPCParticipant);
+    Durable2PCStub();
+}
+
+and
+
+public class Volatile2PCStub extends ParticipantStub
+    implements Volatile2PCParticipant
+{
+    public Volatile2PCStub(String id, W3CEndpointReference twoPCParticipant)
+}
+
+These subclasses add no extra behaviours. However, they both provide a
+constructor which allows an id and endpoint reference for the
+originating participant's participant management service to be
+associated with the stub. Volatile2PCStub is not expected to be
+involved in commit or recovery processing (even though it redundantly
+implements commit(), rollback(), unknown(), saveState() and
+restoreState() these should never be called).
+
+Durable2PCStub is involved in commit/rollback processing and recovery.
+It implements a second, 'empty' constructor allowing the stub to be
+recreated during recovery as a precursor to invoking the restore_state
+method.
+
+Saving of durable participant stub recovery state is performed
+automatically by the WS-AT coordinator at prepare. The participant
+state is bundled in with the coordinator (transaction) state whenever
+the latter is written to disk. This ensures that a recovered WS-AT
+transaction can be recreated with all necessary participant
+information in case of a coordinator crash during commit or rollback
+processing.
+
+These three classes exist in two different flavours, one for WS-AT 1.0
+and another for WS-AT 1.1. The 1.0 versions exist in package
+com.arjuna.wst.stub while the 1.1. versions are in package
+com.arjuna.wst11.stub. This split is necessary because the endpoint
+reference type for 1.0 is from Kev's hand-rolled SOAP stack whereas
+the endpoint for 1.1 is a W3CEndpointReference from the app server's
+WS implementation.
+
+WS-AT Participant Side Recovery
+-------------------------------
+
+In order to be able to perform participant side recovery the XTS
+implementation must:
+
+  -- save details of durable participants at prepare time
+
+  -- delete saved details of durable participants at commit time
+
+  -- identify saved durable participant details during crash recovery
+     and recreate a durable participant from the saved details
+
+Saving, restoring and deletion of participant data, including the
+application-specific recovery state, is managed by the WS-AT
+implementation. However, recreation of saved participants during
+recovery involves creating and initialising instances of
+application-specific classes using the saved recovery state. Since
+this requires loading application-specific classes it must be done by
+application code. Hence, it will be necessary for the XTS
+implementation to provide a registration mechanism allowing
+applications to provide a recovery module to perform this step in the
+recovery process.
+
+Recovery modules must be able to recognise that saved participant
+details belong to their associated application rather than some other
+application. This will be achieved by requiring participants to employ
+identifiers which are unique to their application (as well as unique
+within all participants created by that application). The id employed
+when the participant is registered will be used for this purpose.
+
+A participant must support saving of its recovery state by
+implementing a method which encodes the recovery state as a byte
+array. At prepare, this byte array will be obtained and written to
+disk by the WS-AT implementation, along with the participant
+identifier and the endpoint of the participant's coordinator. This
+participant recovery record will be deleted at the appropriate point
+during commit or rollback. During recovery processing saved recovery
+records for outstanding transactions will be identified and
+reloaded. The byte array saved in the record will be used to
+reconstruct a durable participant and re-register it as an active
+participant with the participant service using the saved identifier
+and endpoint.
+
+A recovery module must implement a method which discriminates amongst
+candidate identifiers. For those which are associated with its
+application, it will be required to construct a durable participant
+from the saved recovery state byte array. If the recovery module does
+not recognise a participant id then it will be assumed to belong to
+some other application.
+
+Participant API
+---------------
+Durable participants are normally expected to implement the save state
+functionality by implementing interface Serializable. In such cases
+participants will be automatically serialized to a byte stream using
+Java serialization and the resulting byte data is stored in the
+recovery record (specifically, immediately afetr teh call to the
+participant's prepare method the stream will be written by means of a
+single call to ObjectOuputStream.writeObject with the participant as
+argument). Alternatively, if the participant does not implement
+Serializable it can implement the following interface in package
+com.arjuna.wst:
+
+public interface PersistableATParticipant
+{
+    byte[] getRecoveryState() throws Exception;
+}
+
+In this case, getRecoveryState will be invoked immediately after the
+call to the participant's prepare method.
+
+One or other of these interfaces must be implemented, otherwise the
+prepare operation on the participant will fail and the participant's
+rollback method will be invoked.
+
+Participant Recovery Module API
+-------------------------------
+A recovery module must implement the following interface in package
+org.jboss.xts.recovery:
+
+public interface XTSATRecoveryModule
+{
+    public Durable2PCParticipant
+    deserialize(String id, ObjectInputStream stream) throws Exception; 
+
+    public Durable2PCParticipant
+    recreate(String id, byte[] recoveryState) throws Exception; 
+}
+
+If a participant was saved using serialization then deserialize will
+be called to allow the module a chance to deserialize it. If a
+participant was saved by invoking the getRecoveryState method of
+PersistableATParticipant then recreate will be called to recreate it.
+Note that either method may be called since the recovery module may be
+asked to recreate participants which belong to other applications so
+it must expect either method to be called even if the application only
+uses one specific means of saving its participant instances.
+
+id is the identifier under which the participant was registered. the
+recovery module should only attempt to reconstruct a durable
+participant if it recognizes the id. stream is stream from which the
+application can read its participant by invoking
+ObjectInputStream.readObject. recoveryState is a byte array containing
+the data returned by getRecoveryState.
+
+Whichever method is called, if it returns null then it is assumed that
+the recovery record does not belong to the recovery module's
+application. If an exception is thrown then it is assumed that the
+record does belong to the module's application and a warning is
+logged. Although this may indicate that the recovery data has been
+corrupted the participant recovery record is not deleted. The recovery
+thread will retry the reconstruction operation when it next runs in
+case the error is a transient one whic can be recovered from. In such
+cases participant records must be deleted from the log manually by an
+administrator.
+
+An application must register its recovery module during application
+deployment and unregister it during undeployment using the following
+API in package org.jboss.xts.recovery:
+
+public class XTSATRecoveryManager
+{
+    ...
+    public void registerRecoveryModule(XTSATRecoveryModule module);
+    public void unregisterRecoveryModule(XTSATRecoveryModule module);
+    ...
+}
+
+WS-AT Participant Recovery State Processing
+-------------------------------------------
+
+An XTS WS-AT participant recovery record is modelled in-memory by
+class ATParticipantRecord. Each record will contain details of a
+single participant: a String identifier; an XML format String
+representation of the associated coordinator endpint reference
+(inlcuding any reference parameters); and a byte[] participant
+recovery state. No subordinate state is required so no persistence
+record types need be defined by the participant recovery system.
+
+Since the endpoint reference format differs between the 1.0 and 1.1
+implementations and since recovery processing will differ depending
+upon the protocol in use it is necessary to implement this class in
+two flavours, one for the WS-AT 1.0 protocol and another for the 1.1
+protocol.
+
+Class ATParticipantRecord implements the PersistableParticipant
+interface enabling the standard TX object state read_committed and
+write_committed operations to be used for creation, retrieval and
+deletion of the object store entry containing the record. Where
+appropriate delegation to methods on the 1.0 or 1.1 subclasses is used
+to generate teh required disk data representation. The saved state
+includes details of the implementation class so that on recovery a
+valid in-memory version of the record can be reconstructed.
+
+XTS WS-AT participant records are saved as top-level TX store entries,
+stored in a location in the TX object store defined by the XTS WS-AT
+participant record type (akin to the ACCoordinator type used on the
+coordinator side to store XTS WS-AT coordinator (transaction)
+state). WS-AT participant recovery records differ from other object
+store entries in that they are not derived from AtopmicAction nor from
+StateManager. The former class is only appropriate as a superclass of
+transactions. The latter class is only appropriate as a superclass of
+classes which will be saved and restored when an Action is current in
+the saving/restoring thread context and that is not the case in the
+threads which handle Participant service incoming messages (*** is
+this actually correct -- need to check ***). Note, however, that this
+should present no problem in managing the object store entries in the
+absence of heuristic outcomes. A participant recovery record will only
+be written at prepare and recovery processing should always ensure
+that a commit or rollback eventually causes the entry to be
+deleted. It does present an issue for any tools provided to scan the
+object store as these need to be aware of the existence of AT recovery
+records and need to support manual deletion of records in the case
+that a crashed coordinator cannot proceed to recovery.
+
+During normal operation the XTS implementation maintains an active
+WS-AT participant map, an in-memory map from WS-AT participant ids to
+WS-AT participant engines. An engine represents an active participant
+and is the primary target for incoming prepare, commit and rollback
+messages. The map is updated during enlist, prepare, commit and
+rollback to reflect the presence of: active participants; and saved or
+deleted WS-AT participant recovery records in the TX object store.
+
+During bootstrap the XTS recovery module will scan for all XTS WS-AT
+participant recovery records. Each WS-AT record will be reconstructed
+in memory and be entered into a recovered participant map, an
+in-memory map from WS-AT participant ids to in-memory participant
+recovery records. Once all records have been scanned and loaded into
+the recovery map each of the XTS WS-AT recovery modules registered
+with the XTS implementation will be used to try to restore the durable
+participant from teh recovery state in each of the participant
+recovery records.
+
+If a module sucessfully converts an entry to a DurableParticipant the
+recovery record will be atomically removed from the recovery map and a
+WS-AT participant engine entered into the active participant map. If
+no module successfully converts the entry to a DurableParticipant a
+recovery warning will be generated and the entry will be left in the
+recovered participant map for conversion on a later recovery pass,
+possibly by a newly registered recovery module (registration may not
+be complete before the first recovery scan is completed).
+n.b. existing modules get another bite at the cherry i.e. the recovery
+recrods is passed to all modules registered at the time of the scan,
+not jsut to those whic hhave been registered since the last scan. This
+covers the case where the failure to initially process the record was
+because the module needed to wait on other internal/external resources
+to start up.
+
+An incoming commit or rollback message may contain a participant id
+which is not found in the WS-AT active participant map. This may
+happen during bootstrap because the initial recovery scan is not
+complete. It may also happen after the initial scan because the
+recovery state has not yet been converted to a participant. The former
+case can be detected using a flag which defaults to false and is set
+to true after the first scan is completed. In the latter case there
+will be an entry for the participant in the recovered participant map.
+
+In these two cases incoming commit or rollback requests will be
+silently dropped to ensure that a valid response is provided when
+recovery is ready. If neither of these exemptions applies it is
+assumed that the message has been resent and processing follows the
+transition tables in the WS-AT spec: the response to a commit request
+will be to send a commited response (i.e. to presume that the commit
+has been resent); the response to a rollback request will be to send
+an aborted response (i.e. to presume that the rollback has been
+resent).
+
+
+WS-AT Coordinator Side Recovery
+-------------------------------
+
+In order to be able to perform coordinator side recovery the WS-AT
+implementation must:
+
+  -- save details of durable participant stubs at prepare
+
+  -- delete saved details of durable participant stubs at commit time
+
+  -- update saved details of durable participant stubs at if a commit
+     stalls par way through or if it completes with a heuristic
+     outcome.
+
+  -- identify saved durable participant stub details during crash
+     recovery and recreate a durable participant stub from the saved
+     details
+
+Saving, restoring and deletion of participant recovery state is
+managed by the WS-AT ACCoordinator class using the participant list
+management capabilities of class TwoPhaseCoordinator. Participant
+stubs are referenced from ParticipantRecord instances located in the
+coordinators participant list. At prepare the details or each active
+stub are converted to a byte format and composed to construct the
+coordinator's saved state, each sub-entry tagged with type XTS_RECORD,
+the type associated with the ParticipantRecord class. The combined
+transaction state is then saved to the TX object store in a location
+associated with the WS-AT ACCoordinator class. This state is normally
+deleted at commit or rollback. It may be rewritten with the results of
+a partial commit process if, say a participant times out, or with the
+details of a heuristic outcome.
+
+During normal operation the XTS implementation maintains an active
+WS-AT participant stub map, an in-memory map from WS-AT participant
+ids to participant stubs. This is used to identify the primary target
+for incoming prepared, committed and aborted requests. The map is
+updated during enlist, prepare and commit/rollback to reflect the
+presence of active participant stubs and saved/deleted WS-AT
+Participant stub records in the TX object store.
+
+During bootstrap the ACCoordinator recovery module recreates XTS WS-AT
+participant stub records as it scans ACCoordinator entries in the TX
+object store. Each WS-AT stub record is entered in the active
+participant map as it is recreated. So, once the first recovery scan
+is completed all active WS-AT participants are entered in the map.
+This point can be detected using a flag which defaults to false and is
+set to true after the first scan is completed.
+
+Up to this point a prepared, committed or aborted message may
+legitimately employ a participant id which is not found in the WS-AT
+active participant map. In such a case incoming commit or rollback
+requests received which do not identify an entry in the map will be
+silently dropped to ensure that a valid response is provided when
+recovery is ready. After this point it is assumed that the message has
+been resent and processing follows the transition tables in the WS-AT
+spec: the response to a prepared message will be to send a rollback
+(i.e. to presume that the message was sent in error); the response to
+a committed or aborted message will be to ignore it (i.e. to presume
+that the message has been resent).

Modified: labs/jbosstm/trunk/XTS/sar/META-INF/jboss-beans.xml
===================================================================
--- labs/jbosstm/trunk/XTS/sar/META-INF/jboss-beans.xml	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/sar/META-INF/jboss-beans.xml	2008-07-30 17:08:39 UTC (rev 21296)
@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <deployment xmlns="urn:jboss:bean-deployer:2.0">
 
-    <bean name="XTSService" class="org.jboss.transactions.XTSService">
+    <bean name="XTSService" class="org.jboss.jbossts.XTSService">
         <annotation>@org.jboss.aop.microcontainer.aspects.jmx.JMX(name="jboss.xts:service=XTSService", exposedInterface=com.arjuna.ats.jbossatx.jta.TransactionManagerServiceMBean.class, registerDirectly=true)</annotation>
 
        <depends>jboss.web:service=WebServer</depends>

Modified: labs/jbosstm/trunk/XTS/sar/build.xml
===================================================================
--- labs/jbosstm/trunk/XTS/sar/build.xml	2008-07-30 15:15:22 UTC (rev 21295)
+++ labs/jbosstm/trunk/XTS/sar/build.xml	2008-07-30 17:08:39 UTC (rev 21296)
@@ -75,10 +75,28 @@
     <property name="build.metainf.dir"       value="${build.dir}/META-INF"/>
     <property name="build.conf.dir" location="${build.dir}/conf"/>
 
+    <property name="jboss.client.lib.jars" value="jaxb-api.jar jboss-javaee.jar jbossws-native-jaxrpc.jar
+            jbossws-native-jaxws.jar jbossws-client.jar stax-api.jar jbossws-native-saaj.jar
+            jbossws-native-jaxws-ext.jar jbossws-native-core.jar jbossws-common.jar jboss-logging-spi.jar"/>
+    <property name="jboss.client.lib.dir" value="${jbossas.home}/client"/>
     <!-- enable debugging of XTS service code -->
 
     <property name="javac.debug" value="on"/>
 
+    <!-- name of the resource bundle -->
+    <property name="xts.resourcebundle"
+          value="xts_msg_en_US.properties"/>
+    <!-- Define buildsystem classpath -->
+    <property name="com.arjuna.buildsystem.lib.jars" value="buildsystem.jar"/>
+
+    <property name="com.arjuna.buildsystem.dir" location="../../antbuildsystem"/>
+    <property name="com.arjuna.buildsystem.lib" location="${com.arjuna.buildsystem.dir}/build/lib"/>
+
+    <path id="com.arjuna.buildsystem.classpath">
+        <fileset dir="${com.arjuna.buildsystem.lib}" includes="${com.arjuna.buildsystem.lib.jars}"/>
+    </path>
+    <property name="com.arjuna.buildsystem.classpath" refid="com.arjuna.buildsystem.classpath"/>
+
     <target name="init">
         <delete dir="${build.dir}"/>
         <mkdir dir="${build.dir}"/>
@@ -94,19 +112,43 @@
     <!-- by default build XTS service archive supporting 1.1 WS-C/T protocols -->
     <target name="sar" depends="sar-11"/>
 
-    <!-- build XTS service archive supporting both 1.0 and 1.1 WS-C/T protocols -->
-    <target name="sar-both" depends="init, insertcfg10, insertcfg11">
-
+    <target name="compile" depends="init">
         <javac srcdir="src" destdir="${build.classes.dir}" debug="${javac.debug}">
             <classpath>
-                <pathelement location="${jbossas.home}/client/jbossall-client.jar"/>
+                <fileset dir="${jboss.client.lib.dir}" includes="${jboss.client.lib.jars}"/>
                 <pathelement location="${jbossas.home}/lib/jboss-system-jmx.jar"/>
-                <!-- sar code only depends on generic XTS code -->
-                <fileset dir="${lib.dir}" includes="*.jar" excludes="*10.jar *11.jar"/>
+                <fileset dir="${lib.dir}" includes="*.jar"/>
                 <fileset dir="${lib.dir}/ext" includes="jbossjta.jar jbossjts.jar jbossts-common.jar"/>
             </classpath>
         </javac>
+    </target>
 
+    <!-- build the messages resource bundle for use by the XTS logger -->
+    <target name="dev-resourcebundle">
+        <echo message="Generating XTS Bundle"/>
+
+        <javadoc    packagenames="org.jboss.jbossts.*"
+                    failonerror="yes"
+                    private="yes"
+                    defaultexcludes="yes"
+                    classpath="${build.classes.dir}">
+
+            <packageset dir="src" defaultexcludes="yes">
+              <include name="org/jboss/jbossts/**"/>
+            </packageset>
+
+             <doclet name="com.hp.mw.buildsystem.doclet.resbundledoclet.ResourceBundleDoclet">
+                <path>
+                    <pathelement path="${com.arjuna.buildsystem.classpath}"/>
+                </path>
+                <param name="-basedir" value="${basedir}/${build.classes.dir}"/>
+                <param name="-resourcebundle" value="${xts.resourcebundle}"/>
+             </doclet>
+        </javadoc>
+    </target>            
+
+    <!-- build XTS service archive supporting both 1.0 and 1.1 WS-C/T protocols -->
+    <target name="sar-both" depends="init, compile, dev-resourcebundle, insertcfg10, insertcfg11">
         <!-- create config jar containing conf files for 1.0 and 1.1 -->
         <filter token="coordinator.hostname" value="${coordinator.hostname}"/>
         <filter token="coordinator.port" value="${coordinator.port}"/>
@@ -168,17 +210,7 @@
     </target>
 
     <!-- build XTS service archive supporting 1.1 WS-C/T protocols -->
-    <target name="sar-11" depends="init, insertcfg11">
-        <javac srcdir="src" destdir="${build.classes.dir}" debug="${javac.debug}">
-            <classpath>
-                <pathelement location="${jbossas.home}/client/jbossall-client.jar"/>
-                <pathelement location="${jbossas.home}/lib/jboss-system-jmx.jar"/>
-                <!-- sar code only depends on generic XTS code -->
-                <fileset dir="${lib.dir}" includes="*.jar" excludes="*10.jar *11.jar"/>
-                <fileset dir="${lib.dir}/ext" includes="jbossjta.jar jbossjts.jar jbossts-common.jar"/>
-            </classpath>
-        </javac>
-
+    <target name="sar-11" depends="init, compile, dev-resourcebundle, insertcfg11">
         <!-- create config jar containing conf files for 1.1 -->
         <filter token="coordinator.hostname" value="${coordinator.hostname}"/>
         <filter token="coordinator.port" value="${coordinator.port}"/>
@@ -231,17 +263,7 @@
     </target>
 
     <!-- build XTS service archive supporting 1.0 WS-C/T protocols -->
-    <target name="sar-10" depends="init, insertcfg10">
-        <javac srcdir="src" destdir="${build.classes.dir}" debug="${javac.debug}">
-            <classpath>
-                <pathelement location="${jbossas.home}/client/jbossall-client.jar"/>
-                <pathelement location="${jbossas.home}/lib/jboss-system-jmx.jar"/>
-                <!-- sar only depends on generic XTS code -->
-                <fileset dir="${lib.dir}" includes="*.jar" excludes="*10.jar *11.jar"/>
-                <fileset dir="${lib.dir}/ext" includes="jbossjta.jar jbossjts.jar jbossts-common.jar"/>
-            </classpath>
-        </javac>
-
+    <target name="sar-10" depends="init, compile, dev-resourcebundle, insertcfg10">
         <!-- create config jar containing conf files for 1.0 -->
         <filter token="coordinator.hostname" value="${coordinator.hostname}"/>
         <filter token="coordinator.port" value="${coordinator.port}"/>

Copied: labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts (from rev 21141, labs/jbosstm/trunk/XTS/sar/src/org/jboss/transactions)

Modified: labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/XTSService.java
===================================================================
--- labs/jbosstm/trunk/XTS/sar/src/org/jboss/transactions/XTSService.java	2008-07-21 09:06:47 UTC (rev 21141)
+++ labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/XTSService.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -18,12 +18,12 @@
  * (C) 2007,
  * @author JBoss Inc.
  */
-package org.jboss.transactions;
+package org.jboss.jbossts;
 
 import org.jboss.logging.Logger;
-import org.jboss.transactions.xts.recovery.ACCoordinatorRecoveryModule;
+import org.jboss.jbossts.xts.recovery.ACCoordinatorRecoveryModule;
+import org.jboss.jbossts.xts.recovery.participant.at.ATParticipantRecoveryModule;
 
-import com.arjuna.mw.wsas.utils.Configuration;
 //import com.arjuna.mw.wst.deploy.WSTXInitialisation;
 //import com.arjuna.mw.wst.UserTransaction;
 //import com.arjuna.mw.wst.TransactionManager;
@@ -74,8 +74,6 @@
 import com.arjuna.ats.arjuna.recovery.RecoveryManager;
 //import com.arjuna.ats.arjuna.recovery.RecoveryModule;
 
-import java.io.InputStream;
-
 /**
  * $Id$
  */
@@ -103,6 +101,7 @@
     private final Logger log = org.jboss.logging.Logger.getLogger(XTSService.class);
 
     private ACCoordinatorRecoveryModule acCoordinatorRecoveryModule = null;
+    private ATParticipantRecoveryModule atParticipantRecoveryModule = null;
 
     // TODO: how to use a (per application) remote coordinator?
     // does the http servlet param indicate its own location and the
@@ -177,10 +176,18 @@
 
         acCoordinatorRecoveryModule.install();
 
+        // we don't need to install anything in the Inventory for this recovery module as it
+        // manages its own ObjectStore records but we do need it to create the recovery manager
+        // singleton.
+
+        atParticipantRecoveryModule = new ATParticipantRecoveryModule();
+
+        atParticipantRecoveryModule.install();
+
         // we assume the tx manager has started, hence initializing the recovery manager.
         // to guarantee this our mbean should depend on the tx mgr mbean. (but does that g/tee start or just load?)
+        RecoveryManager.manager().addModule(atParticipantRecoveryModule);
         RecoveryManager.manager().addModule(acCoordinatorRecoveryModule);
-
     }
 
     public void stop() throws Exception
@@ -188,11 +195,17 @@
         log.info("JBossTS XTS Transaction Service - stopping");
 
         if (acCoordinatorRecoveryModule != null) {
-            // remove the module, making sure no any scan which might be using it has completed
+            // remove the module, making sure any scan which might be using it has completed
             RecoveryManager.manager().removeModule(acCoordinatorRecoveryModule, true);
             // ok, now it is safe to get the recovery manager to uninstall its Implementations from the inventory
             acCoordinatorRecoveryModule.uninstall();
         }
+        if (atParticipantRecoveryModule != null) {
+            // remove the module, making sure any scan which might be using it has completed
+            RecoveryManager.manager().removeModule(atParticipantRecoveryModule, true);
+            // call uninstall even though it is currently a null op for this module
+            atParticipantRecoveryModule.uninstall();
+        }
         TaskManager.getManager().shutdown() ; // com.arjuna.services.framework.admin.TaskManagerInitialisation
 
         /*

Modified: labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/XTSServiceMBean.java
===================================================================
--- labs/jbosstm/trunk/XTS/sar/src/org/jboss/transactions/XTSServiceMBean.java	2008-07-21 09:06:47 UTC (rev 21141)
+++ labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/XTSServiceMBean.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -18,7 +18,7 @@
  * (C) 2007,
  * @author JBoss Inc.
  */
-package org.jboss.transactions;
+package org.jboss.jbossts;
 
 /**
  * $Id$

Added: labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/logging/XTSLogger.java
===================================================================
--- labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/logging/XTSLogger.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/logging/XTSLogger.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -0,0 +1,34 @@
+package org.jboss.jbossts.xts.logging;
+
+import java.util.Locale;
+import java.util.ResourceBundle;
+
+import com.arjuna.ats.arjuna.common.arjPropertyManager;
+import com.arjuna.common.internal.util.logging.commonPropertyManager;
+import com.arjuna.common.util.logging.LogFactory;
+import com.arjuna.common.util.logging.LogNoi18n;
+import com.arjuna.common.util.logging.Logi18n;
+
+public class XTSLogger
+{
+    public static LogNoi18n arjLogger;
+    public static Logi18n arjLoggerI18N;
+    public static ResourceBundle log_mesg;
+
+    static
+    {
+        /** Ensure the properties are loaded before initialising the logger **/
+        arjPropertyManager.getPropertyManager();
+
+    	arjLogger = LogFactory.getLogNoi18n("com.arjuna.webservices.logging.XTSLogger");
+
+        final String language = commonPropertyManager.propertyManager.getProperty("language","en");
+        final String country  = commonPropertyManager.propertyManager.getProperty("country","US");
+
+    	final Locale currentLocale = new Locale(language, country);
+    	log_mesg = ResourceBundle.getBundle("xts_msg",currentLocale);
+
+    	arjLoggerI18N = LogFactory.getLogi18n("com.arjuna.webservices.logging.XTSLoggerI18N",
+    					     "xts_msg_"+language+"_"+country);
+    }
+}

Modified: labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/ACCoordinatorRecoveryModule.java
===================================================================
--- labs/jbosstm/trunk/XTS/sar/src/org/jboss/transactions/xts/recovery/ACCoordinatorRecoveryModule.java	2008-07-21 09:06:47 UTC (rev 21141)
+++ labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/ACCoordinatorRecoveryModule.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -18,11 +18,13 @@
  * (C) 2007,
  * @author Red Hat Middleware LLC.
  */
-package org.jboss.transactions.xts.recovery;
+package org.jboss.jbossts.xts.recovery;
 
+import org.jboss.jbossts.xts.logging.XTSLogger;
+import org.jboss.jbossts.xts.recovery.participant.at.XTSATRecoveryManager;
+
 import com.arjuna.ats.arjuna.recovery.RecoveryModule;
 import com.arjuna.ats.arjuna.recovery.TransactionStatusConnectionManager;
-import com.arjuna.ats.arjuna.logging.tsLogger;
 import com.arjuna.ats.arjuna.logging.FacilityCode;
 import com.arjuna.ats.arjuna.coordinator.TxControl;
 import com.arjuna.ats.arjuna.coordinator.ActionStatus;
@@ -34,7 +36,6 @@
 import com.arjuna.common.util.logging.VisibilityLevel;
 
 import com.arjuna.mwlabs.wscf.model.twophase.arjunacore.ACCoordinator;
-import com.arjuna.webservices.base.processors.ActivatedObjectProcessor;
 import com.arjuna.webservices.base.processors.ReactivatedObjectProcessor;
 
 import java.util.Vector;
@@ -48,22 +49,20 @@
  * (com.arjuna.mwlabs.wscf.model.as.coordinator.arjunacore.ACCoordinator)
  * Modelled on com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule
  * TODO: refactor this and AtomicActionRecoveryModule to remove duplication?
- * TODO: move to better package.
- * TODO: how to register (config file vs. programmatic, given that the module list is fixed once recovery has started)
  *
- * @message com.arjuna.ats.internal.arjuna.recovery.ACCoordinatorRecoveryModule_1 [com.arjuna.ats.internal.arjuna.recovery.ACCoordinatorRecoveryModule_1] - RecoveryManagerStatusModule: Object store exception: {0}
- * @message com.arjuna.ats.internal.arjuna.recovery.ACCoordinatorRecoveryModule_2 [com.arjuna.ats.internal.arjuna.recovery.ACCoordinatorRecoveryModule_2] - failed to recover Transaction {0} {1}
- * @message com.arjuna.ats.internal.arjuna.recovery.ACCoordinatorRecoveryModule_3 [com.arjuna.ats.internal.arjuna.recovery.ACCoordinatorRecoveryModule_3] - failed to access transaction store {0} {1}
+ * $Id$
  *
- * $Id$
+ * @message com.arjuna.ats.internal.arjuna.recovery.ACCoordinatorRecoveryModule_1 [org.jboss.transactions.xts.recovery.ACCoordinatorRecoveryModule_1] - RecoveryManagerStatusModule: Object store exception: {0}
+ * @message org.jboss.transactions.xts.recovery.ACCoordinatorRecoveryModule_2 [org.jboss.transactions.xts.recovery.ACCoordinatorRecoveryModule_2] - failed to recover Transaction {0} {1}
+ * @message org.jboss.transactions.xts.recovery.ACCoordinatorRecoveryModule_3 [org.jboss.transactions.xts.recovery.ACCoordinatorRecoveryModule_3] - failed to access transaction store {0} {1}
  */
 public class ACCoordinatorRecoveryModule  implements RecoveryModule
 {
     public ACCoordinatorRecoveryModule()
     {
-        if (tsLogger.arjLogger.isDebugEnabled())
+        if (XTSLogger.arjLogger.isDebugEnabled())
         {
-            tsLogger.arjLogger.debug
+            XTSLogger.arjLogger.debug
                     ( DebugLevel.CONSTRUCTORS,
                             VisibilityLevel.VIS_PUBLIC,
                             FacilityCode.FAC_CRASH_RECOVERY,
@@ -108,9 +107,9 @@
 
         try
         {
-            if (tsLogger.arjLogger.isDebugEnabled())
+            if (XTSLogger.arjLogger.isDebugEnabled())
             {
-                tsLogger.arjLogger.debug( "StatusModule: first pass " );
+                XTSLogger.arjLogger.debug( "StatusModule: first pass " );
             }
 
             ACCoordinators = _transactionStore.allObjUids( _transactionType, acc_uids );
@@ -118,9 +117,9 @@
         }
         catch ( ObjectStoreException ex )
         {
-            if (tsLogger.arjLoggerI18N.isWarnEnabled())
+            if (XTSLogger.arjLoggerI18N.isWarnEnabled())
             {
-                tsLogger.arjLoggerI18N.warn("com.arjuna.ats.internal.arjuna.recovery.ACCoordinatorRecoveryModule_1",
+                XTSLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.ACCoordinatorRecoveryModule_1",
                         new Object[]{ex});
             }
         }
@@ -133,25 +132,22 @@
 
     public void periodicWorkSecondPass()
     {
-        if (tsLogger.arjLogger.isDebugEnabled())
+        if (XTSLogger.arjLogger.isDebugEnabled())
         {
-            tsLogger.arjLogger.debug( "ACCoordinatorRecoveryModule: Second pass " );
+            XTSLogger.arjLogger.debug( "ACCoordinatorRecoveryModule: Second pass " );
         }
 
         processTransactionsStatus() ;
 
-        // ok we will have left a ghost record in the reactivated object table for any
-        // entries still sitting in the log so we can safely reject messages for unknown,
-        // non-ghost identifiers
+        // ok notify the coordinator processor that recovery processing has completed
 
-        ReactivatedObjectProcessor.setReactivationProcessingStarted();
     }
 
     protected ACCoordinatorRecoveryModule (String type)
     {
-        if (tsLogger.arjLogger.isDebugEnabled())
+        if (XTSLogger.arjLogger.isDebugEnabled())
         {
-            tsLogger.arjLogger.debug
+            XTSLogger.arjLogger.debug
                     ( DebugLevel.CONSTRUCTORS,
                             VisibilityLevel.VIS_PUBLIC,
                             FacilityCode.FAC_CRASH_RECOVERY,
@@ -173,15 +169,19 @@
         boolean commitThisTransaction = true ;
 
         // Retrieve the transaction status from its original process.
+        // n.b. for a non-active XTS TX this status wil l always be committed even
+        // if it aborted or had a heuristic outcome. in that case we need to use
+        // the logged action status which can only be retrieved after activation
+
         int theStatus = _transactionStatusConnectionMgr.getTransactionStatus( _transactionType, recoverUid ) ;
 
         boolean inFlight = isTransactionInMidFlight( theStatus ) ;
 
         String Status = ActionStatus.stringForm( theStatus ) ;
 
-        if (tsLogger.arjLogger.isDebugEnabled())
+        if (XTSLogger.arjLogger.isDebugEnabled())
         {
-            tsLogger.arjLogger.debug
+            XTSLogger.arjLogger.debug
                     ( DebugLevel.FUNCTIONS,
                             VisibilityLevel.VIS_PUBLIC,
                             FacilityCode.FAC_CRASH_RECOVERY,
@@ -194,11 +194,11 @@
         {
             try
             {
-                tsLogger.arjLogger.debug( DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC,
+                XTSLogger.arjLogger.debug( DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC,
                             FacilityCode.FAC_CRASH_RECOVERY, "jjh doing revovery here for "+recoverUid);
                 // TODO jjh
                 RecoverACCoordinator rcvACCoordinator =
-                        new RecoverACCoordinator(recoverUid, theStatus);
+                        new RecoverACCoordinator(recoverUid);
 //                RecoverAtomicAction rcvAtomicAction =
 //                        new RecoverAtomicAction( recoverUid, theStatus ) ;
 
@@ -207,9 +207,9 @@
             }
             catch ( Exception ex )
             {
-                if (tsLogger.arjLoggerI18N.isWarnEnabled())
+                if (XTSLogger.arjLoggerI18N.isWarnEnabled())
                 {
-                    tsLogger.arjLoggerI18N.warn("com.arjuna.ats.internal.arjuna.recovery.ACCoordinatorRecoveryModule_2",
+                    XTSLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.ACCoordinatorRecoveryModule_2",
                             new Object[]{recoverUid.toString(), ex});
                 }
             }
@@ -256,9 +256,9 @@
     {
         Vector uidVector = new Vector() ;
 
-        if (tsLogger.arjLogger.isDebugEnabled())
+        if (XTSLogger.arjLogger.isDebugEnabled())
         {
-            tsLogger.arjLogger.debug( DebugLevel.FUNCTIONS,
+            XTSLogger.arjLogger.debug( DebugLevel.FUNCTIONS,
                     VisibilityLevel.VIS_PUBLIC,
                     FacilityCode.FAC_CRASH_RECOVERY,
                     "processing " + _transactionType
@@ -283,9 +283,9 @@
                 {
                     Uid newUid = new Uid( theUid ) ;
 
-                    if (tsLogger.arjLogger.isDebugEnabled())
+                    if (XTSLogger.arjLogger.isDebugEnabled())
                     {
-                        tsLogger.arjLogger.debug
+                        XTSLogger.arjLogger.debug
                                 ( DebugLevel.FUNCTIONS,
                                         VisibilityLevel.VIS_PUBLIC,
                                         FacilityCode.FAC_CRASH_RECOVERY,
@@ -321,13 +321,15 @@
             }
             catch ( ObjectStoreException ex )
             {
-                if (tsLogger.arjLogger.isWarnEnabled())
+                if (XTSLogger.arjLogger.isWarnEnabled())
                 {
-                    tsLogger.arjLoggerI18N.warn("com.arjuna.ats.internal.arjuna.recovery.ACCoordinatorRecoveryModule_3",
+                    XTSLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.ACCoordinatorRecoveryModule_3",
                             new Object[]{currentUid.toString(), ex});
                 }
             }
         }
+
+        XTSATRecoveryManager.getRecoveryManager().setCoordinatorRecoveryStarted();
     }
 
     // 'type' within the Object Store for ACCoordinator.

Modified: labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/Implementations.java
===================================================================
--- labs/jbosstm/trunk/XTS/sar/src/org/jboss/transactions/xts/recovery/Implementations.java	2008-07-21 09:06:47 UTC (rev 21141)
+++ labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/Implementations.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -19,7 +19,7 @@
  * @author JBoss Inc.
  */
 
-package org.jboss.transactions.xts.recovery;
+package org.jboss.jbossts.xts.recovery;
 
 import com.arjuna.ats.arjuna.gandiva.inventory.Inventory;
 

Modified: labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/ParticipantRecordSetup.java
===================================================================
--- labs/jbosstm/trunk/XTS/sar/src/org/jboss/transactions/xts/recovery/ParticipantRecordSetup.java	2008-07-21 09:06:47 UTC (rev 21141)
+++ labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/ParticipantRecordSetup.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -18,13 +18,11 @@
  * (C) 2005-2006,
  * @author JBoss Inc.
  */
-package org.jboss.transactions.xts.recovery;
+package org.jboss.jbossts.xts.recovery;
 
 import com.arjuna.ats.arjuna.gandiva.inventory.InventoryElement;
 import com.arjuna.ats.arjuna.gandiva.ClassName;
 import com.arjuna.ats.arjuna.gandiva.ObjectName;
-import com.arjuna.ats.arjuna.PersistenceRecord;
-import com.arjuna.ats.arjuna.ArjunaNames;
 import com.arjuna.ats.arjuna.coordinator.RecordType;
 
 import com.arjuna.mwlabs.wscf.model.twophase.arjunacore.ParticipantRecord;

Modified: labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/RecoverACCoordinator.java
===================================================================
--- labs/jbosstm/trunk/XTS/sar/src/org/jboss/transactions/xts/recovery/RecoverACCoordinator.java	2008-07-21 09:06:47 UTC (rev 21141)
+++ labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/RecoverACCoordinator.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -1,7 +1,8 @@
-package org.jboss.transactions.xts.recovery;
+package org.jboss.jbossts.xts.recovery;
 
+import org.jboss.jbossts.xts.logging.XTSLogger;
+
 import com.arjuna.ats.arjuna.common.Uid;
-import com.arjuna.ats.arjuna.logging.tsLogger;
 import com.arjuna.ats.arjuna.logging.FacilityCode;
 import com.arjuna.ats.arjuna.coordinator.ActionStatus;
 
@@ -13,9 +14,10 @@
  * This class is a plug-in module for the recovery manager.
  * It is responsible for recovering failed ACCoordinator transactions.
  *
- * @message com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule_1 [com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule_1] - RecoveryManagerStatusModule: Object store exception: {0}
- * @message com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule_2 [com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule_2] - failed to recover Transaction {0} {1}
- * @message com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule_3 [com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule_3] - failed to access transaction store {0} {1}
+ * @message org.jboss.transactions.xts.recovery.RecoverACCoordinator_1 [org.jboss.transactions.xts.recovery.RecoverACCoordinator_1] - RecoverACCoordinator.replayPhase2 recovering {0} ActionStatus is {1}
+ * @message org.jboss.transactions.xts.recovery.RecoverACCoordinator_2 [org.jboss.transactions.xts.recovery.RecoverACCoordinator_2] - RecoverACCoordinator.replayPhase2: Unexpected status: {0}
+ * @message org.jboss.transactions.xts.recovery.RecoverACCoordinator_3 [org.jboss.transactions.xts.recovery.RecoverACCoordinator_3] - RecoverACCoordinator.replayPhase2( {0} )  finished
+ * @message org.jboss.transactions.xts.recovery.RecoverACCoordinator_4 [org.jboss.transactions.xts.recovery.RecoverACCoordinator_4] - RecoverACCoordinator.replayPhase2 transaction {0} not activated, unable to replay phase 2 commit
 */
 public class RecoverACCoordinator extends ACCoordinator {
 
@@ -25,10 +27,9 @@
     * Re-creates/activates an AtomicAction for the specified
     * transaction Uid.
     */
-   public RecoverACCoordinator ( Uid rcvUid, int theStatus )
+   public RecoverACCoordinator ( Uid rcvUid )
    {
       super( rcvUid ) ;
-      _theStatus = theStatus ;
       _activated = activate() ;
    }
 
@@ -37,59 +38,54 @@
     */
    public void replayPhase2()
    {
-       if (tsLogger.arjLoggerI18N.debugAllowed())
+       final int status = status();
+
+       if (XTSLogger.arjLoggerI18N.debugAllowed())
        {
-	   tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC,
+	   XTSLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC,
 					FacilityCode.FAC_CRASH_RECOVERY,
-					"com.arjuna.ats.arjuna.recovery.RecoverAtomicAction_1",
-					new Object[]{get_uid(), ActionStatus.stringForm(_theStatus)});
+					"org.jboss.transactions.xts.recovery.RecoverACCoordinator_1",
+					new Object[]{get_uid(), ActionStatus.stringForm(status)});
        }
 
        if ( _activated )
        {
-	   if ( (_theStatus == ActionStatus.PREPARED) ||
-		(_theStatus == ActionStatus.COMMITTING) ||
-		(_theStatus == ActionStatus.COMMITTED) ||
-		(_theStatus == ActionStatus.H_COMMIT) ||
-		(_theStatus == ActionStatus.H_MIXED) ||
-		(_theStatus == ActionStatus.H_HAZARD) )
+           // we only need to rerun phase2 if the action status is  PREPARED, which happens
+           // when we crash before or during commit, or COMMITTING, which happens when we get
+           // a comms timeout from one of the participants after sending it a COMMIT message.
+           // in the former case all participant records will be listed in the prepared list.
+           // in the latter case the failed participant record(s) will have been reinstated in the
+           // prepared list and the participant stub engine reactivated, where necessary,
+           // under the call to activate() when this coordinator was created.
+
+           // we can also arrive here when the action status is ABORTING. This happens when we
+           // get a comms timeout from one of the participants after sending it a PREPARE message
+           // or if we get a comms timeout from one of the participants after sending it a ROLLBACK
+           // message. In either case the failed participant record(s) will be listed in the heuristic
+           // list. Since this list is ignored completely by phase2Abort there is no point doing
+           // anything here. there are also cases where actionStatus is a heuristic outcome. Once
+           // again it is pointless calling phase2Abort since the prepared list is empty. 
+
+       if ((status == ActionStatus.PREPARED) ||
+               (status == ActionStatus.COMMITTING))
 	   {
 	       super.phase2Commit( _reportHeuristics ) ;
 	   }
-	   else if ( (_theStatus == ActionStatus.ABORTED) ||
-		     (_theStatus == ActionStatus.H_ROLLBACK) ||
-		     (_theStatus == ActionStatus.ABORTING) ||
-		     (_theStatus == ActionStatus.ABORT_ONLY) )
-	   {
-	       super.phase2Abort( _reportHeuristics ) ;
-	   }
-	   else
-	   {
-	       if (tsLogger.arjLoggerI18N.isWarnEnabled())
-	       {
-		   tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.recovery.RecoverAtomicAction_2",
-					       new Object[]{ActionStatus.stringForm(_theStatus)});
-	       }
-	   }
 
-	   if (tsLogger.arjLoggerI18N.debugAllowed())
+	   if (XTSLogger.arjLoggerI18N.debugAllowed())
 	   {
-	       tsLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC,
+	       XTSLogger.arjLoggerI18N.debug(DebugLevel.FUNCTIONS, VisibilityLevel.VIS_PUBLIC,
 					    FacilityCode.FAC_CRASH_RECOVERY,
-					    "com.arjuna.ats.arjuna.recovery.RecoverAtomicAction_3",
+					    "org.jboss.transactions.xts.recovery.RecoverACCoordinator_3",
 					    new Object[]{get_uid()});
 	   }
        }
        else
        {
-	   tsLogger.arjLoggerI18N.warn("com.arjuna.ats.arjuna.recovery.RecoverAtomicAction_4");
+	   XTSLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.RecoverACCoordinator_4", new Object[]{get_uid()});
        }
    }
 
-   // Current transaction status
-   // (retrieved from the TransactionStatusManager)
-   private int _theStatus ;
-
    // Flag to indicate that this transaction has been re-activated
    // successfully.
    private boolean _activated = false ;

Copied: labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/participant/at/ATParticipantRecoveryModule.java (from rev 21141, labs/jbosstm/trunk/XTS/sar/src/org/jboss/transactions/xts/recovery/ACCoordinatorRecoveryModule.java)
===================================================================
--- labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/participant/at/ATParticipantRecoveryModule.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/participant/at/ATParticipantRecoveryModule.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -0,0 +1,331 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2007, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags.
+ * See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ * This copyrighted material is made available to anyone wishing to use,
+ * modify, copy, or redistribute it subject to the terms and conditions
+ * of the GNU Lesser General Public License, v. 2.1.
+ * This program is distributed in the hope that it will be useful, but WITHOUT A
+ * 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,
+ * v.2.1 along with this distribution; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA  02110-1301, USA.
+ *
+ * (C) 2007,
+ * @author Red Hat Middleware LLC.
+ */
+package org.jboss.jbossts.xts.recovery.participant.at;
+
+import org.jboss.jbossts.xts.logging.XTSLogger;
+
+import com.arjuna.ats.arjuna.recovery.RecoveryModule;
+import com.arjuna.ats.arjuna.logging.FacilityCode;
+import com.arjuna.ats.arjuna.coordinator.TxControl;
+import com.arjuna.ats.arjuna.state.InputObjectState;
+import com.arjuna.ats.arjuna.exceptions.ObjectStoreException;
+import com.arjuna.ats.arjuna.common.Uid;
+import com.arjuna.ats.arjuna.objectstore.ObjectStore;
+import com.arjuna.common.util.logging.DebugLevel;
+import com.arjuna.common.util.logging.VisibilityLevel;
+
+import java.util.Vector;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.io.IOException;
+
+import org.jboss.jbossts.xts.recovery.participant.at.ATParticipantRecoveryRecord;
+
+/**
+ * This class is a plug-in module for the recovery manager.
+ * It is responsible for recovering failed XTS (ACCoordinator) transactions.
+ *
+ * Responsible for recovering instances of XTS Transaction Coordinators
+ * (com.arjuna.mwlabs.wscf.model.as.coordinator.arjunacore.ACCoordinator)
+ * Modelled on com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule
+ * TODO: refactor this and AtomicActionRecoveryModule to remove duplication?
+ *
+ * $Id$
+ *
+ * @message org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_1 [org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_1] - RecoveryManagerStatusModule: Object store exception: {0}
+ * @message org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_3 [org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_3] - failed to access transaction store {0}
+ * @message org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_4 [org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_4] - unable to load recovery record implementation class {0} for WS-AT participant recovery record {1}
+ * @message org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_5 [org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_5] - unable to instantiate recovery record implementation class {0} for WS-AT participant recovery record {1}
+ * @message org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_6 [org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_6] - unable to unpack recovery record data for WS-AT participant recovery record {0}
+ * @message org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_7 [org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_7] - missing recovery record data for WS-AT participant recovery record {0}
+ * @message org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_8 [org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_8] - unable to read recovery record data for WS-AT participant recovery record {0}
+ */
+
+public class ATParticipantRecoveryModule implements RecoveryModule
+{
+    public ATParticipantRecoveryModule()
+    {
+        if (XTSLogger.arjLogger.isDebugEnabled())
+        {
+            XTSLogger.arjLogger.debug
+                    ( DebugLevel.CONSTRUCTORS,
+                            VisibilityLevel.VIS_PUBLIC,
+                            FacilityCode.FAC_CRASH_RECOVERY,
+                            "ATParticipantRecoveryModule created - default" );
+        }
+
+        if (_objectStore == null)
+        {
+            _objectStore = TxControl.getStore() ;
+        }
+
+        _participantType = ATParticipantRecoveryRecord.type();
+    }
+
+    /**
+     * called by the service startup code before the recovery module is added to the recovery managers
+     * module list
+     */
+    public void install()
+    {
+        XTSATRecoveryManager.setRecoveryManager(new XTSATRecoveryManagerImple(_objectStore));
+    }
+
+    /**
+     * called by the service shutdown code after the recovery module is removed from the recovery managers
+     * module list
+     */
+    public void uninstall()
+    {
+    }
+
+    /**
+     * This is called periodically by the RecoveryManager
+     */
+    public void periodicWorkFirstPass()
+    {
+        // Transaction type
+        boolean ATParticipants = false ;
+
+        // uids per transaction type
+        InputObjectState acc_uids = new InputObjectState() ;
+
+        try
+        {
+            if (XTSLogger.arjLogger.isDebugEnabled())
+            {
+                XTSLogger.arjLogger.debug( "StatusModule: first pass " );
+            }
+
+            ATParticipants = _objectStore.allObjUids(_participantType, acc_uids );
+
+        }
+        catch ( ObjectStoreException ex )
+        {
+            if (XTSLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                XTSLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_1",
+                        ex);
+            }
+        }
+
+        if ( ATParticipants )
+        {
+            _participantUidVector = processParticipants( acc_uids ) ;
+        }
+    }
+
+    public void periodicWorkSecondPass()
+    {
+        if (XTSLogger.arjLogger.isDebugEnabled())
+        {
+            XTSLogger.arjLogger.debug( "ATParticipantRecoveryModule: Second pass " );
+        }
+
+        processParticipantsStatus() ;
+    }
+
+    private void doRecoverParticipant( Uid recoverUid )
+    {
+        // Retrieve the participant from its original process.
+
+        if (XTSLogger.arjLogger.isDebugEnabled())
+        {
+            XTSLogger.arjLogger.debug
+                    ( DebugLevel.FUNCTIONS,
+                            VisibilityLevel.VIS_PUBLIC,
+                            FacilityCode.FAC_CRASH_RECOVERY,
+                            "participant type is "+ _participantType + " uid is " +
+                                    recoverUid.toString()) ;
+        }
+
+        // we don't need to use a lock here because we only attempt the read
+        // when the uid is inactive which means it cannto be pulled out form under our
+        // feet at commit. uniqueness of uids also means we can't be foiled by a reused
+        // uid.
+
+        XTSATRecoveryManager recoveryManager = XTSATRecoveryManager.getRecoveryManager();
+
+        if (!recoveryManager.isParticipantPresent(recoverUid)) {
+            // ok, the participant can neither be active nor loaded awaiting recreation by
+            // an application recovery module so we need to load it
+            try {
+                // retrieve the data for the participant
+                InputObjectState inputState = _objectStore.read_committed(recoverUid, _participantType);
+
+                if (inputState != null) {
+                    try {
+                        String participantRecordClazzName = inputState.unpackString();
+                        try {
+                            // create a participant engine instance and tell it to recover itself
+                            Class participantRecordClazz = Class.forName(participantRecordClazzName);
+                            ATParticipantRecoveryRecord participantRecord = (ATParticipantRecoveryRecord)participantRecordClazz.newInstance();
+                            participantRecord.restoreState(inputState);
+                            // ok, now insert into recovery map if needed
+                            XTSATRecoveryManager.getRecoveryManager().addParticipantRecoveryRecord(recoverUid, participantRecord);
+                        } catch (ClassNotFoundException cnfe) {
+                            // oh boy, not supposed to happen -- n.b. either the user deployed 1.0
+                            // last time and 1.1 this time or vice versa or something is rotten in
+                            // the state of Danmark
+                            if (XTSLogger.arjLoggerI18N.isErrorEnabled())
+                            {
+                                XTSLogger.arjLoggerI18N.error("org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_4",
+                                        new Object[]{participantRecordClazzName, recoverUid.toString()}, cnfe);
+                            }
+                        } catch (InstantiationException ie) {
+                            // this is also worrying, log an error
+                            if (XTSLogger.arjLoggerI18N.isErrorEnabled())
+                            {
+                                XTSLogger.arjLoggerI18N.error("org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_5",
+                                        new Object[]{participantRecordClazzName, recoverUid.toString()}, ie);
+                            }
+                        } catch (IllegalAccessException iae) {
+                            // this is another configuration problem, log an error
+                            if (XTSLogger.arjLoggerI18N.isErrorEnabled())
+                            {
+                                XTSLogger.arjLoggerI18N.error("org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_5",
+                                        new Object[]{participantRecordClazzName, recoverUid.toString()}, iae);
+                            }
+                        }
+                    } catch (IOException ioe) {
+                        // hmm, record corrupted? log this as a warning
+                        if (XTSLogger.arjLoggerI18N.isWarnEnabled())
+                        {
+                            XTSLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_6",
+                                    new Object[]{recoverUid.toString()}, ioe);
+                        }
+                    }
+                } else {
+                    // hmm, it ought not to be able to disappear unless the recovery manager knows about it
+                    // this is an error!
+                    if (XTSLogger.arjLoggerI18N.isErrorEnabled())
+                    {
+                        XTSLogger.arjLoggerI18N.error("org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_7",
+                                new Object[]{recoverUid.toString()});
+                    }
+                }
+            } catch (ObjectStoreException ose) {
+                // if the object store is not working this is serious
+                if (XTSLogger.arjLoggerI18N.isErrorEnabled())
+                {
+                    XTSLogger.arjLoggerI18N.error("org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_8",
+                            new Object[]{recoverUid.toString()}, ose);
+                }
+            }
+        }
+    }
+
+    private Vector processParticipants( InputObjectState uids )
+    {
+        Vector uidVector = new Vector() ;
+
+        if (XTSLogger.arjLogger.isDebugEnabled())
+        {
+            XTSLogger.arjLogger.debug( DebugLevel.FUNCTIONS,
+                    VisibilityLevel.VIS_PUBLIC,
+                    FacilityCode.FAC_CRASH_RECOVERY,
+                    "processing " + _participantType
+                            + " WS-AT participants" ) ;
+        }
+
+        Uid theUid = new Uid( Uid.nullUid() );
+
+        boolean moreUids = true ;
+
+        while (moreUids)
+        {
+            try
+            {
+                theUid.unpack( uids ) ;
+
+                if (theUid.equals( Uid.nullUid() ))
+                {
+                    moreUids = false;
+                }
+                else
+                {
+                    Uid newUid = new Uid( theUid ) ;
+
+                    if (XTSLogger.arjLogger.isDebugEnabled())
+                    {
+                        XTSLogger.arjLogger.debug
+                                ( DebugLevel.FUNCTIONS,
+                                        VisibilityLevel.VIS_PUBLIC,
+                                        FacilityCode.FAC_CRASH_RECOVERY,
+                                        "found WS-AT participant "+ newUid ) ;
+                    }
+
+                    uidVector.addElement( newUid ) ;
+                }
+            }
+            catch ( Exception ex )
+            {
+                moreUids = false;
+            }
+        }
+        return uidVector ;
+    }
+
+    private void processParticipantsStatus()
+    {
+        // Process the Vector of transaction Uids
+        Enumeration participantUidEnum = _participantUidVector.elements() ;
+
+        while ( participantUidEnum.hasMoreElements() )
+        {
+            Uid currentUid = (Uid) participantUidEnum.nextElement();
+
+            try
+            {
+                if ( _objectStore.currentState( currentUid, _participantType) != ObjectStore.OS_UNKNOWN )
+                {
+                    doRecoverParticipant( currentUid ) ;
+                }
+            }
+            catch ( ObjectStoreException ex )
+            {
+                if (XTSLogger.arjLogger.isWarnEnabled())
+                {
+                    XTSLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.participant.at.ATParticipantRecoveryModule_3",
+                            new Object[]{currentUid.toString()}, ex);
+                }
+            }
+        }
+
+        // now get the AT recovery manager to try to activate recovered participants
+
+        XTSATRecoveryManager.getRecoveryManager().recoverParticipants();
+    }
+
+    // 'type' within the Object Store for ATParticipant record.
+    private String _participantType = ATParticipantRecoveryRecord.type() ;
+
+    // Array of transactions found in the object store of the
+    // ACCoordinator type.
+    private Vector _participantUidVector = null ;
+
+    // Reference to the Object Store.
+    private static ObjectStore _objectStore = null ;
+
+    // This object provides information about whether or not a participant is currently active.
+
+    private HashMap<String, ATParticipantRecoveryRecord> _recoveredParticipantMap ;
+}
\ No newline at end of file

Added: labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/participant/at/XTSATRecoveryManagerImple.java
===================================================================
--- labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/participant/at/XTSATRecoveryManagerImple.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/sar/src/org/jboss/jbossts/xts/recovery/participant/at/XTSATRecoveryManagerImple.java	2008-07-30 17:08:39 UTC (rev 21296)
@@ -0,0 +1,343 @@
+package org.jboss.jbossts.xts.recovery.participant.at;
+
+import org.jboss.jbossts.xts.logging.XTSLogger;
+
+import com.arjuna.wst.Durable2PCParticipant;
+import com.arjuna.ats.arjuna.objectstore.ObjectStore;
+import com.arjuna.ats.arjuna.state.OutputObjectState;
+import com.arjuna.ats.arjuna.common.Uid;
+import com.arjuna.ats.arjuna.exceptions.ObjectStoreException;
+
+import java.util.*;
+import java.io.IOException;
+
+/**
+ * A class which manages the table of recovered participant records.
+ *
+ * @message org.jboss.transactions.xts.recovery.participant.at.XTSATRecoveryModule_1 [org.jboss.transactions.xts.recovery.participant.at.XTSATRecoveryModule_1] exception writing recovery record for WS-AT participant {0}
+ * @message org.jboss.transactions.xts.recovery.participant.at.XTSATRecoveryModule_2 [org.jboss.transactions.xts.recovery.participant.at.XTSATRecoveryModule_2] exception removing recovery record {0} for WS-AT participant {1}
+ * @message org.jboss.transactions.xts.recovery.participant.at.XTSATRecoveryModule_3 [org.jboss.transactions.xts.recovery.participant.at.XTSATRecoveryModule_3] exception reactivating recovered WS-AT participant {0}
+ * @message org.jboss.transactions.xts.recovery.participant.at.XTSATRecoveryModule_4 [org.jboss.transactions.xts.recovery.participant.at.XTSATRecoveryModule_4] no XTS application recovery module found to help reactivate recovered WS-AT participant {0}
+ */
+public class XTSATRecoveryManagerImple extends XTSATRecoveryManager {
+    /**
+     * constructor for use by ATParticipantRecoveryModule
+     * @param objectStore
+     */
+    XTSATRecoveryManagerImple(ObjectStore objectStore)
+    {
+        this.objectStore = objectStore;
+    }
+
+    /**
+     * register an application specific recovery module which acts as a helper to recreate
+     * a WS-AT durable participant from the participant's recovery data saved at prepare
+     *
+     * @param module the module which will be used to identify and recreate participants
+     *               for the application
+     * @throws NullPointerException if the supplied module is null
+     */
+    public void registerRecoveryModule(XTSATRecoveryModule module) throws NullPointerException
+    {
+        // TODO other sanity checks?
+        if (module == null) {
+            throw new NullPointerException("XTSATRecoveryModule value must be non-null");
+        }
+
+        recoveryModules.add(module);
+    }
+
+    /**
+     * unregister an application specific recovery module previously registered as
+     * a helper to recretae WS-AT durable participants
+     *
+     * @param module the module to be unregistered
+     * @throws java.util.NoSuchElementException
+     *          if the module is not currently registered
+     */
+    public void unregisterRecoveryModule(XTSATRecoveryModule module) throws NoSuchElementException {
+        if (!recoveryModules.remove(module)) {
+            throw new NoSuchElementException();
+        }
+    }
+
+    /**
+     * save the supplied participant recovery record to persistent storage
+     *
+     * @param participantRecoveryRecord
+     */
+    public boolean writeParticipantRecoveryRecord(ATParticipantRecoveryRecord participantRecoveryRecord)
+    {
+        OutputObjectState oos = new OutputObjectState();
+        // we need to be able to retrieve the class of the participant record so we can
+        // create an instancde to load the rest of the participant specific data
+        try {
+            oos.packString(participantRecoveryRecord.getClass().getCanonicalName());
+        } catch (IOException ioe) {
+            if (XTSLogger.arjLoggerI18N.isWarnEnabled())
+            {
+                XTSLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.participant.at.XTSATRecoveryModule_1",
+                        new Object[] {participantRecoveryRecord.getId()}, ioe);
+            }
+            return false;
+        }
+
+        if (participantRecoveryRecord.saveState(oos)) {
+            Uid uid = new Uid();
+            try {
+                objectStore.write_committed(uid, type, oos);
+                // we need to be able to identify the uid from the participant id
+                // in order to delete it later
+                uidMap.put(participantRecoveryRecord.getId(), uid);
+                return true;
+            } catch (ObjectStoreException ose) {
+                if (XTSLogger.arjLoggerI18N.isWarnEnabled())
+                {
+                    XTSLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.participant.at.XTSATRecoveryModule_1",
+                            new Object[] {participantRecoveryRecord.getId()}, ose);
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * remove any participant recovery record with the supplied id from persistent storage
+     * @param id the id of the participant whose recovery details are being deleted
+     */
+    public boolean deleteParticipantRecoveryRecord(String id)
+    {
+        Uid uid = uidMap.get(id);
+
+        if (uid != null) {
+
+            try {
+                objectStore.remove_committed(uid, type);
+                uidMap.remove(id);
+                return true;
+            } catch (ObjectStoreException ose) {
+                if (XTSLogger.arjLoggerI18N.isWarnEnabled())
+                {
+                    XTSLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.participant.at.XTSATRecoveryModule_2",
+                            new Object[] {uid, id},
+                            ose);
+                }
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * test whether the supplied uid identifies an active participant or a recovered but inactive
+     * participant
+     *
+     * n.b. this method is not synchronized because of two assumptions: first, that uids are
+     * never reused; and second that only recovery scanning (as a minimum, for a given recovery
+     * record type) is single-threaded. Correctness of the first assumption ensures there are no
+     * races with participant processor threads, the second races between recovery threads.
+     *
+     * @param uid
+     */
+    public boolean isParticipantPresent(Uid uid)
+    {
+        return (uidMap.get(uid) != null); 
+    }
+
+    /**
+     * add a recovered participant record to the table of unrecovered participants which
+     * need to be recreated following recovery
+     *
+     * @param uid the uid under which the participant was saved in the file store
+     * @param participantRecoveryRecord the in-memory representation of the recovery record
+     * saved to disk
+     */
+    public void addParticipantRecoveryRecord(Uid uid, ATParticipantRecoveryRecord participantRecoveryRecord)
+    {
+        String participantId = participantRecoveryRecord.getId();
+        if (recoveryMap.get(participantId) == null && !participantRecoveryRecord.isActive()) {
+            // ok, we have not seen this entry before so add it to the list
+            recoveryMap.put(participantId, participantRecoveryRecord);
+            uidMap.put(participantId, uid);
+        }
+    }
+
+    /**
+     * see if a participant recovery record with a given id exists in the table of participants which
+     * need to be recreated following recovery
+     * @param id the identifier of the participant being sought
+     * @return the participant recovery record with the supplied id or null if it is not found
+     */
+    public synchronized ATParticipantRecoveryRecord findParticipantRecoveryRecord(String id)
+    {
+        return recoveryMap.get(id);
+    }
+
+    /**
+     * process all entries in the recovered participant map and attempt to recreate the
+     * application participant and activate it
+     */
+    public void recoverParticipants()
+    {
+        // the first scan has been performed so allow processing of commit and rollback requests
+        // for unknown ids to proceed now
+
+        setParticipantRecoveryStarted();
+
+        // we operate on a copy of the recovery modules to avoid the list being modified
+        // by register and unregister operations while we are iterating over it
+        // we should probably also make sure unregister does not proceed until
+        // the current scan is complete . . .
+
+        List<XTSATRecoveryModule> recoveryModulesCopy;
+        synchronized (recoveryModules) {
+            recoveryModulesCopy = new ArrayList<XTSATRecoveryModule>(recoveryModules);
+        }
+
+        // iterate through the participant recovery records and try to convert them to
+        // a durable participant. if successful activate the participant and then remove the
+        // recovery entry. note that since recovery is single threaded we can be sure that
+        // no concurrent modifications will be made to the table while we are iterating and,
+        // possibly, deleting via the iterator
+
+        Iterator<ATParticipantRecoveryRecord> participantIterator = iterator();
+
+        while(participantIterator.hasNext()) {
+            ATParticipantRecoveryRecord participantRecoveryRecord = participantIterator.next();
+            if (participantRecoveryRecord.isActive()) {
+                // this participant must have already been activated by a by a previous
+                // scan and been reloaded by this scan so just remove the entry
+
+                participantIterator.remove();
+            } else {
+                Iterator<XTSATRecoveryModule> moduleIterator = recoveryModulesCopy.iterator();
+                boolean found = false;
+
+                while (!found && moduleIterator.hasNext()) {
+                    XTSATRecoveryModule module = moduleIterator.next();
+                    try {
+                        if (participantRecoveryRecord.restoreParticipant(module)) {
+                            // ok, this participant has recovered so tell it to
+                            // activate and *then* remove it from the hashmap. this makes
+                            // sure we don't open a window where an incoming
+                            // commit may fail to find the object in either table
+
+                            found = true;
+                            participantRecoveryRecord.activate();
+
+                            participantIterator.remove();
+                        }
+                    } catch (Exception e) {
+                        // we foudn a helper but it failed to convert the participant record -- log a warning
+                        // but leave the participant in the table for next time in case the helper has merely
+                        // suffered a transient failure
+                        found = true;
+                        if (XTSLogger.arjLoggerI18N.isWarnEnabled())
+                        {
+                            XTSLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.participant.at.XTSATRecoveryModule_3",
+                                    new Object[] {participantRecoveryRecord.getId()},
+                                    e);
+                        }
+                    }
+                }
+
+                if (!found) {
+                    // we failed to find a helper to convert a participant record so log a warning
+                    // but leave it in the table for next time
+                    if (XTSLogger.arjLoggerI18N.isWarnEnabled())
+                    {
+                        XTSLogger.arjLoggerI18N.warn("org.jboss.transactions.xts.recovery.participant.at.XTSATRecoveryModule_4",
+                                new Object[] {participantRecoveryRecord.getId()});
+                    }
+                }
+            }
+        }
+    }
+    
+    /**
+     * return an iterator over the collection of entries in the table. n.b. this iterates
+     * direct over the table so any deletions performed during iteration need to be done
+     * via the iterator and need to be sure to avoid concurrent modification
+     * @return
+     */
+    private synchronized Iterator<ATParticipantRecoveryRecord> iterator()
+    {
+        return recoveryMap.values().iterator();
+    }
+
+    /**
+     * set a global flag indicating that the first AT participant recovery scan has
+     * been performed.
+     */
+    private synchronized void setParticipantRecoveryStarted()
+    {
+        participantRecoveryStarted = true;
+    }
+
+    /**
+     * test whether the first AT participant recovery scan has completed. this indicates whether
+     * there may or may not still be unknown participant recovery records on disk. If the first
+     * scan has not yet completed then a commit or rollback message for an unknown participant
+     * must be dropped. If it has then a commit or rollback for an unknown participant must be
+     * acknowledged with, respectively, a committed or aborted message.
+     */
+    public synchronized boolean isParticipantRecoveryStarted()
+    {
+        return participantRecoveryStarted;
+    }
+
+    /**
+     * test whether the first AT coordinator recovery scan has completed. this indicates whether
+     * there may or may not still be unknown AT transcation records on disk. If the first
+     * scan has not yet completed then a prepare message for an unknown participant
+     * must be dropped. If it has then a perpare for an unknown participant must be
+     * acknowledged with a rollback message.
+     */
+    public synchronized boolean isCoordinatorRecoveryStarted() {
+        return coordinatorRecoveryStarted;
+    }
+
+    /**
+     * record the fact that the first AT coordinator recovery scan has completed.
+     */
+
+    public synchronized void setCoordinatorRecoveryStarted() {
+        coordinatorRecoveryStarted = true;
+    }
+
+    /**
+     * a global flag indicating whether the first AT participant recovery scan has
+     * been performed.
+     */
+    private boolean participantRecoveryStarted = false;
+
+    /**
+     * a global flag indicating whether the first AT coordinator recovery scan has
+     * been performed.
+     */
+    private boolean coordinatorRecoveryStarted = false;
+
+    /**
+     * a map from participant ids to participant recovery records
+     */
+    private HashMap<String, ATParticipantRecoveryRecord> recoveryMap = new HashMap<String, ATParticipantRecoveryRecord>();
+
+    /**
+     * a map from participant id to the uid under which the participant has been saved in the
+     * persistent store
+     */
+    private HashMap<String, Uid> uidMap = new HashMap<String, Uid>();
+
+    /**
+     * a map from participant ids to participant recover records
+     */
+    private List<XTSATRecoveryModule> recoveryModules = new ArrayList<XTSATRecoveryModule>();
+
+    /**
+     * the tx object store to be used for saving and deleting participant details
+     */
+    private ObjectStore objectStore;
+
+    private final static String type = ATParticipantRecoveryRecord.type();
+}




More information about the jboss-svn-commits mailing list