[jboss-svn-commits] JBL Code SVN: r21312 - in labs/jbosstm/trunk/XTS: demo/ddrpc/jboss and 5 other directories.

jboss-svn-commits at lists.jboss.org jboss-svn-commits at lists.jboss.org
Thu Jul 31 09:27:12 EDT 2008


Author: adinn
Date: 2008-07-31 09:27:11 -0400 (Thu, 31 Jul 2008)
New Revision: 21312

Added:
   labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/recovery/
   labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/recovery/DemoRPCATRecoveryListener.java
   labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/recovery/DemoRPCATRecoveryModule.java
Modified:
   labs/jbosstm/trunk/XTS/WS-T/dev/src10/org/jboss/jbossts/xts10/recovery/participant/at/ATParticipantRecoveryRecord.java
   labs/jbosstm/trunk/XTS/demo/ddrpc/jboss/service-web-app.xml
   labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Restaurant/RestaurantManager.java
   labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Restaurant/RestaurantParticipantAT.java
   labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Restaurant/RestaurantServiceAT.java
   labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Taxi/TaxiManager.java
   labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Taxi/TaxiParticipantAT.java
   labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Taxi/TaxiServiceAT.java
   labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Theatre/TheatreManager.java
   labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Theatre/TheatreParticipantAT.java
   labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Theatre/TheatreServiceAT.java
Log:
udpated JaxRPC-based demo to implement participant persistence in order to exercise 1.0 participant recovery which seems to work ok -- for JBTM-121

Modified: 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	2008-07-31 12:30:46 UTC (rev 21311)
+++ labs/jbosstm/trunk/XTS/WS-T/dev/src10/org/jboss/jbossts/xts10/recovery/participant/at/ATParticipantRecoveryRecord.java	2008-07-31 13:27:11 UTC (rev 21312)
@@ -7,8 +7,10 @@
 import com.arjuna.webservices.wsaddr.EndpointReferenceType;
 import com.arjuna.webservices.wsat.State;
 import com.arjuna.webservices.wsat.processors.ParticipantProcessor;
+import com.arjuna.webservices.util.StreamHelper;
 
 import javax.xml.stream.*;
+import javax.xml.namespace.QName;
 import java.io.StringWriter;
 import java.io.IOException;
 import java.io.StringReader;
@@ -49,7 +51,9 @@
         XMLOutputFactory factory = XMLOutputFactory.newInstance();
         StringWriter stringWriter = new StringWriter();
         XMLStreamWriter writer = factory.createXMLStreamWriter(stringWriter);
+        StreamHelper.writeStartElement(writer, QNAME_TWO_PC_COORDINATOR) ;
         endpoint.writeContent(writer);
+        StreamHelper.writeEndElement(writer, null, null) ;
         writer.close();
         oos.packString(stringWriter.toString());
     }
@@ -63,6 +67,7 @@
         StringReader stringReader = new StringReader(xmlEndpoint);
         XMLInputFactory factory = XMLInputFactory.newInstance();
         XMLStreamReader reader = factory.createXMLStreamReader(stringReader);
+        StreamHelper.checkNextStartTag(reader, QNAME_TWO_PC_COORDINATOR) ;
         endpoint = new EndpointReferenceType(reader);
     }
 
@@ -85,5 +90,7 @@
         return ParticipantProcessor.getProcessor().isActive(getId());
     }
 
+    private static final QName QNAME_TWO_PC_COORDINATOR = new QName("twoPCCoordinator") ;
+
     private EndpointReferenceType endpoint;
 }

Modified: labs/jbosstm/trunk/XTS/demo/ddrpc/jboss/service-web-app.xml
===================================================================
--- labs/jbosstm/trunk/XTS/demo/ddrpc/jboss/service-web-app.xml	2008-07-31 12:30:46 UTC (rev 21311)
+++ labs/jbosstm/trunk/XTS/demo/ddrpc/jboss/service-web-app.xml	2008-07-31 13:27:11 UTC (rev 21312)
@@ -24,6 +24,9 @@
 	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
 	version="2.4">
 
+    <listener>
+        <listener-class>com.arjuna.xts.nightout.services.recovery.DemoRPCATRecoveryListener</listener-class>
+    </listener>
 	<servlet>
 		<servlet-name>RestaurantServiceAT</servlet-name>
 		<servlet-class>com.arjuna.xts.nightout.services.Restaurant.RestaurantServiceAT</servlet-class>

Modified: labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Restaurant/RestaurantManager.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Restaurant/RestaurantManager.java	2008-07-31 12:30:46 UTC (rev 21311)
+++ labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Restaurant/RestaurantManager.java	2008-07-31 13:27:11 UTC (rev 21312)
@@ -30,6 +30,8 @@
 package com.arjuna.xts.nightout.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)
         {
@@ -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 used to store the restaurant manager state
+     */
+    final static private String STATE_FILENAME = "restaurantManagerRPCState";
+
+    /**
+     * the name of the file used to store the restaurant manager shadow state
+     */
+    final static private String SHADOW_STATE_FILENAME = "restaurantManagerRPCShadowState";
+
+    /**
+     * 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/srcrpc/com/arjuna/xts/nightout/services/Restaurant/RestaurantParticipantAT.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Restaurant/RestaurantParticipantAT.java	2008-07-31 12:30:46 UTC (rev 21311)
+++ labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Restaurant/RestaurantParticipantAT.java	2008-07-31 13:27:11 UTC (rev 21312)
@@ -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/srcrpc/com/arjuna/xts/nightout/services/Restaurant/RestaurantServiceAT.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Restaurant/RestaurantServiceAT.java	2008-07-31 12:30:46 UTC (rev 21311)
+++ labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Restaurant/RestaurantServiceAT.java	2008-07-31 13:27:11 UTC (rev 21312)
@@ -66,7 +66,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-demorpc:restaurantAT:" + new Uid().toString());
             }
         }
         catch (Exception e)

Modified: labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Taxi/TaxiManager.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Taxi/TaxiManager.java	2008-07-31 12:30:46 UTC (rev 21311)
+++ labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Taxi/TaxiManager.java	2008-07-31 13:27:11 UTC (rev 21312)
@@ -30,6 +30,8 @@
 package com.arjuna.xts.nightout.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>changes 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 = "taxiManagerRPCState";
+
+    /**
+     * the name of the file sued to store the restaurant manager shadow state
+     */
+    final static private String SHADOW_STATE_FILENAME = "taxiManagerRPCShadowState";
+
+    /**
+     * 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/srcrpc/com/arjuna/xts/nightout/services/Taxi/TaxiParticipantAT.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Taxi/TaxiParticipantAT.java	2008-07-31 12:30:46 UTC (rev 21311)
+++ labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Taxi/TaxiParticipantAT.java	2008-07-31 13:27:11 UTC (rev 21312)
@@ -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/srcrpc/com/arjuna/xts/nightout/services/Taxi/TaxiServiceAT.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Taxi/TaxiServiceAT.java	2008-07-31 12:30:46 UTC (rev 21311)
+++ labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Taxi/TaxiServiceAT.java	2008-07-31 13:27:11 UTC (rev 21312)
@@ -64,7 +64,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-demorpc:taxiAT:" + new Uid().toString());
             }
         }
         catch (Exception e)

Modified: labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Theatre/TheatreManager.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Theatre/TheatreManager.java	2008-07-31 12:30:46 UTC (rev 21311)
+++ labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Theatre/TheatreManager.java	2008-07-31 13:27:11 UTC (rev 21312)
@@ -30,6 +30,8 @@
 package com.arjuna.xts.nightout.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 = "theatreManagerRPCState";
+
+    /**
+     * the name of the file sued to store the restaurant manager shadow state
+     */
+    final static private String SHADOW_STATE_FILENAME = "theatreManagerRPCShadowState";
+
+    /**
+     * 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/srcrpc/com/arjuna/xts/nightout/services/Theatre/TheatreParticipantAT.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Theatre/TheatreParticipantAT.java	2008-07-31 12:30:46 UTC (rev 21311)
+++ labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Theatre/TheatreParticipantAT.java	2008-07-31 13:27:11 UTC (rev 21312)
@@ -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/srcrpc/com/arjuna/xts/nightout/services/Theatre/TheatreServiceAT.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Theatre/TheatreServiceAT.java	2008-07-31 12:30:46 UTC (rev 21311)
+++ labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/Theatre/TheatreServiceAT.java	2008-07-31 13:27:11 UTC (rev 21312)
@@ -66,7 +66,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-demorpc:theatreAT:" + new Uid().toString());
             }
         }
         catch (Exception e)

Copied: labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/recovery/DemoRPCATRecoveryListener.java (from rev 21296, labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/recovery/DemoATRecoveryListener.java)
===================================================================
--- labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/recovery/DemoRPCATRecoveryListener.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/recovery/DemoRPCATRecoveryListener.java	2008-07-31 13:27:11 UTC (rev 21312)
@@ -0,0 +1,21 @@
+package com.arjuna.xts.nightout.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 DemoRPCATRecoveryListener implements ServletContextListener
+{
+
+    public void contextInitialized(ServletContextEvent event) {
+        DemoRPCATRecoveryModule.register();
+    }
+
+    public void contextDestroyed(ServletContextEvent event) {
+        DemoRPCATRecoveryModule.unregister();
+    }
+}
\ No newline at end of file

Copied: labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/recovery/DemoRPCATRecoveryModule.java (from rev 21296, labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/recovery/DemoATRecoveryModule.java)
===================================================================
--- labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/recovery/DemoRPCATRecoveryModule.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/demo/srcrpc/com/arjuna/xts/nightout/services/recovery/DemoRPCATRecoveryModule.java	2008-07-31 13:27:11 UTC (rev 21312)
@@ -0,0 +1,103 @@
+package com.arjuna.xts.nightout.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 DemoRPCATRecoveryModule implements XTSATRecoveryModule
+{
+    /**
+     * the singleton recovery module
+     */
+    private static DemoRPCATRecoveryModule 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 DemoRPCATRecoveryModule();
+        }
+        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-demorpc:restaurantAT") ||
+                id.startsWith("org.jboss.jbossts.xts-demorpc:theatreAT") ||
+                id.startsWith("org.jboss.jbossts.xts-demorpc:taxiAT")) {
+            System.out.println("xts-demorpc : attempting to deserialize WS-AT participant " + id);
+            Durable2PCParticipant participant = (Durable2PCParticipant)stream.readObject();
+            System.out.println("xts-demorpc : 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-demorpc:restauarantAT") ||
+                id.startsWith("org.jboss.jbossts.xts-demorpc:theatreAT") ||
+                id.startsWith("org.jboss.jbossts.xts-demorpc:taxiAT")) {
+            // this should not get called -- xts-demo WS-AT participants are saved and restored
+            // using serialization
+            throw new Exception("xts-demorpc : invalid request to recreate() WS-AT participant " + id);
+        }
+        return null;
+    }
+}
\ No newline at end of file




More information about the jboss-svn-commits mailing list