[jboss-svn-commits] JBL Code SVN: r35189 - in labs/jbosstm/trunk/XTS/demo: dd/scripts and 6 other directories.

jboss-svn-commits at lists.jboss.org jboss-svn-commits at lists.jboss.org
Mon Sep 20 06:11:04 EDT 2010


Author: adinn
Date: 2010-09-20 06:11:03 -0400 (Mon, 20 Sep 2010)
New Revision: 35189

Added:
   labs/jbosstm/trunk/XTS/demo/dd/scripts/
   labs/jbosstm/trunk/XTS/demo/dd/scripts/demokill.txt
   labs/jbosstm/trunk/XTS/demo/dd/scripts/demorecover.txt
   labs/jbosstm/trunk/XTS/demo/dd/scripts/demotrace.txt
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantConstants.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantState.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/state/
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/state/ServiceState.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/state/ServiceStateManager.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreConstants.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreState.java
Modified:
   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/DemoBARecoveryModule.java
   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/RestaurantParticipantBA.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/RestaurantServiceBA.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/TaxiParticipantBA.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/TaxiServiceBA.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/TheatreParticipantBA.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/TheatreServiceBA.java
   labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreView.java
Log:
modified demo to employ a proper local transactional store for web
service state. this also entails using the new recovery functionality
APIs. fixes for JBTM-790

Added: labs/jbosstm/trunk/XTS/demo/dd/scripts/demokill.txt
===================================================================
--- labs/jbosstm/trunk/XTS/demo/dd/scripts/demokill.txt	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/demo/dd/scripts/demokill.txt	2010-09-20 10:11:03 UTC (rev 35189)
@@ -0,0 +1,108 @@
+# kill the XTS demo after preparing the AT theatre participant
+# or after preparing the BA theatre participant
+#
+# this script crashes the XTS demo at one of 4 points depending upon
+# the value of system property demo.kill.point, which should be
+# set to one of the string values "1", "2", "3" or "4". The four
+# kill points are
+#
+# 1 just before the theatre service prepares local changes
+# 2 just before XTS logs the theatre recovery record
+# 3 just before the theatre service commits local changes
+# 4 just after the theatre service commits local changes
+#
+# At each point may be a participant record for each service (PR, PT)
+# and/or a shadow state file for each service (SR, ST) as follows
+#
+# AT 1 SR, PR
+# AT 2 SR, PR, ST
+# AT 3 ST, PT
+# AT 4 PT
+#
+# BA 1 PR
+# BA 2 PR, ST
+# BA 3 PR, ST, PT
+# BA 4 PR, PT
+#
+# In all cases the transactions should be rolled back/and or compensated
+# but recovery processing will vary according to the presence of the
+# participant record and shadow state file
+#
+# AT 1 -- restaurant participant should be recovered and restaurant
+# shadow state should be cleared under participant rollback
+#
+# AT 2 -- restaurant participant should be recovered and restaurant
+# shadow state should be cleared under participant rollback. theatre
+# shadow state should be cleared at end of recovery scan
+#
+# AT 3 -- theatre participant should be recovered and theatre shadow
+# state should be cleared under participant rollback.
+#
+# AT 4 -- theatre participant should be recovered but participant
+# rollback should find no theatre shadow state to clear
+
+# BA 1 -- restaurant participant should be recovered and compensated
+#
+# BA 2 -- restaurant participant should be recovered and compensated.
+# theatre shadow state should be cleared at end of recovery scan.
+#
+# BA 3 -- restaurant participant should be recovered and compensated.
+# theatre participant should be recovered, theatre shadow state should
+# be committed and theatre participant should be compensated.
+#
+# BA 4 -- restaurant participant should be recovered and
+# compensated. theatre participant should be recovered and compensated
+
+# open the trace file
+RULE open trace file
+CLASS ServiceStateManager
+METHOD <init>
+IF openTrace("log", "trace.out")
+DO traceln("log", "started logging kill  -- point = " + System.getProperty("demo.kill.point"))
+ENDRULE
+
+# kill the demo just before the theatre service prepares local changes
+RULE kill at kill point 1
+CLASS TheatreManager
+METHOD prepareSeats
+AT CALL writeShadowState
+BIND killPoint =  System.getProperty("demo.kill.point")
+IF killPoint == "1"
+DO traceln("killing JVM at point " + killPoint),
+   killJVM()
+ENDRULE
+
+# kill the demo just before XTS logs the recovery record
+RULE kill at kill point 2
+CLASS TheatreManager
+METHOD prepareSeats
+AT RETURN
+BIND killPoint =  System.getProperty("demo.kill.point")
+IF killPoint == "2"
+DO traceln("killing JVM at point " + killPoint),
+   killJVM()
+ENDRULE
+
+# kill the demo just before the theatre service commits local changes
+RULE kill at kill point 3
+CLASS TheatreManager
+METHOD commitSeats
+AT ENTRY
+BIND killPoint =  System.getProperty("demo.kill.point"),
+     nullValue : Object = null
+IF killPoint == "3" || killPoint == nullValue
+DO traceln("killing JVM at point " + killPoint),
+   killJVM()
+ENDRULE
+
+# kill the demo just before XTS notifies COMPLETED
+RULE kill at kill point 4
+CLASS TheatreManager
+METHOD commitSeats
+AT RETURN
+BIND killPoint =  System.getProperty("demo.kill.point")
+IF killPoint == "4"
+DO traceln("killing JVM at point " + killPoint),
+   killJVM()
+ENDRULE
+

Added: labs/jbosstm/trunk/XTS/demo/dd/scripts/demorecover.txt
===================================================================
--- labs/jbosstm/trunk/XTS/demo/dd/scripts/demorecover.txt	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/demo/dd/scripts/demorecover.txt	2010-09-20 10:11:03 UTC (rev 35189)
@@ -0,0 +1,106 @@
+# trace recovery in the XTS demo after killing the demo
+# before returning from the second (theatre service) prepare
+# the point at which the JVM is killed can be either
+# 1 just before the theatre service prepares local changes
+# 2 just before XTS logs the recovery record
+# 3 just before the theatre service commits local changes
+# 4 just before XTS notifies COMPLETED
+
+# fiddle the first recovery wait so it does not take all day
+RULE speed up first recovery wait
+CLASS PeriodicRecovery
+METHOD doPeriodicWait
+AT ENTRY
+IF !flagged("firstPeriodicRecoveryWait")
+DO flag("firstPeriodicRecoveryWait"),
+   Thread.sleep(30 * 1000),
+   RETURN
+ENDRULE
+
+# open the trace file
+RULE open trace file
+CLASS ServiceStateManager
+METHOD <init>
+IF openTrace("log", "trace.out")
+DO traceln("log", "*** started logging recovery")
+ENDRULE
+
+
+# track restoration of service state from disk
+# triggered in 1-4, current will always be non-null
+# shadow will be null for restaurant and null for
+# theatre service in 1,4 and non-null in 2,3
+
+RULE trace restore state
+CLASS ServiceStateManager
+METHOD restoreState
+AT RETURN
+IF TRUE
+DO traceln("log", "restore state for " + $0),
+   traceln("log", "preparedTxId =  " + $shadowTxId),
+   traceln("log", "current =  " + $current),
+   traceln("log", "shadow =  " + $shadow)
+ENDRULE
+
+# track recovery of restaurant participant
+# triggered in 1-4
+RULE trace restaurant recovery
+CLASS RestaurantManager
+METHOD recovered
+AT EXIT
+IF TRUE
+DO traceln("log", "recovered restaurant participant " + $1)
+ENDRULE
+
+# track call to confirm completed for recovered restaurant participant
+# which has prepared but not yet committed its local changes
+# never triggered in 1-4
+RULE trace confirm completed for recovered restaurant participant
+CLASS RestaurantManager
+METHOD recovered
+AT CALL confirmCompleted
+IF TRUE
+DO traceln("log", "confirming complete for " + $1 + " " + $*[0])
+ENDRULE
+
+# track recovery of theatre participant
+# triggered in 2-4
+RULE trace theatre recovery
+CLASS TheatreManager
+METHOD recovered
+AT EXIT
+IF TRUE
+DO traceln("log", "recovered theatre participant " + $1)
+ENDRULE
+
+# track call to confirm completed for recovered theatre participant
+# which has prepared but not yet committed its local changes
+# triggered in 3
+RULE trace confirm completed for recovered theatre participant
+CLASS TheatreManager
+METHOD recovered
+AT CALL confirmCompleted
+IF TRUE
+DO traceln("log", "confirming complete for " + $1 + " " + $*[0])
+ENDRULE
+
+# track call to commitSeats in theatre
+# triggered in 3 for BA
+RULE trace commit seats under local recovery in theatre participant
+CLASS TheatreManager
+METHOD commitSeats
+IF callerEquals("confirmCompleted")
+AT CALL commitShadowState
+DO traceln("log", "committing shadow theatre state in " + $1)
+ENDRULE
+
+# track call to rollbackSeats in theatre
+# triggered in 2 for AT and BA
+RULE trace rollback seats under local recovery in theatre participant
+CLASS TheatreManager
+METHOD rollbackSeats
+AT CALL clearShadowState
+IF callerEquals("recoveryScanCompleted") OR
+   callerEquals("confirmCompleted")
+DO traceln("log", "rolling back shadow theatre state in " + $1)
+ENDRULE

Added: labs/jbosstm/trunk/XTS/demo/dd/scripts/demotrace.txt
===================================================================
--- labs/jbosstm/trunk/XTS/demo/dd/scripts/demotrace.txt	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/demo/dd/scripts/demotrace.txt	2010-09-20 10:11:03 UTC (rev 35189)
@@ -0,0 +1,31 @@
+# extra trace rules for debugging
+#
+# track write shadow state
+# just for debug
+RULE trace write shadow state
+CLASS ServiceStateManager
+METHOD writeShadowState
+IF TRUE
+DO traceln("log", "*** tx : " + $1 + " writing shadow state " + $2),
+   traceStack(null, "log", 5)
+ENDRULE
+
+# track commit shadow state
+# just for debug
+RULE trace write commit shadow state
+CLASS ServiceStateManager
+METHOD commitShadowState
+IF TRUE
+DO traceln("log", "*** tx : " + $1 + " commit shadow state "),
+   traceStack(null, "log", 5)
+ENDRULE
+
+# track clear shadow state
+# just for debug
+RULE trace clear commit shadow state
+CLASS ServiceStateManager
+METHOD clearShadowState
+IF TRUE
+DO traceln("log", "*** tx : " + $1 + " clear shadow state "),
+   traceStack(null, "log", 5)
+ENDRULE

Modified: 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	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/recovery/DemoATRecoveryModule.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -1,5 +1,12 @@
 package com.jboss.jbosstm.xts.demo.services.recovery;
 
+import com.jboss.jbosstm.xts.demo.services.restaurant.RestaurantManager;
+import com.jboss.jbosstm.xts.demo.services.restaurant.RestaurantParticipantAT;
+import com.jboss.jbosstm.xts.demo.services.state.ServiceStateManager;
+import com.jboss.jbosstm.xts.demo.services.taxi.TaxiManager;
+import com.jboss.jbosstm.xts.demo.services.taxi.TaxiParticipantAT;
+import com.jboss.jbosstm.xts.demo.services.theatre.TheatreManager;
+import com.jboss.jbosstm.xts.demo.services.theatre.TheatreParticipantAT;
 import org.jboss.jbossts.xts.recovery.participant.at.XTSATRecoveryModule;
 import org.jboss.jbossts.xts.recovery.participant.at.XTSATRecoveryManager;
 import com.arjuna.wst.Durable2PCParticipant;
@@ -64,14 +71,26 @@
      * @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);
+    public Durable2PCParticipant deserialize(String id, ObjectInputStream stream) throws Exception
+    {
+        if (id.startsWith("org.jboss.jbossts.xts-demo:restaurantAT")) {
+            System.out.println("xts-demo : attempting to deserialize RestaurantParticipantAT " + id);
+            RestaurantParticipantAT participant = (RestaurantParticipantAT)stream.readObject();
+            System.out.println("xts-demo : deserialized RestaurantParticipantAT " + id);
+            RestaurantManager.getSingletonInstance().recovered(participant);
             return participant;
+        } else if (id.startsWith("org.jboss.jbossts.xts-demo:theatreAT")) {
+            System.out.println("xts-demo : attempting to deserialize TheatreParticipantAT " + id);
+            TheatreParticipantAT participant = (TheatreParticipantAT)stream.readObject();
+            System.out.println("xts-demo : deserialized TheatreParticipantAT " + id);
+            TheatreManager.getSingletonInstance().recovered(participant);
+            return participant;
+        } else if (id.startsWith("org.jboss.jbossts.xts-demo:taxiAT")) {
+            System.out.println("xts-demo : attempting to deserialize TaxiParticipantAT " + id);
+            TaxiParticipantAT participant = (TaxiParticipantAT)stream.readObject();
+            System.out.println("xts-demo : deserialized TaxiParticipantAT " + id);
+            TaxiManager.getSingletonInstance().recovered(participant);
+            return participant;
         }
 
         return null;
@@ -100,4 +119,27 @@
         }
         return null;
     }
+
+    /**
+     * flags used to identify the first endScan call.
+     */
+    private boolean isFirst = true;
+
+    /**
+     * participant recovery modules may need to perform special processing when the a recovery scan has
+     * completed. in particular it is only after the first recovery scan has completed they can identify
+     * whether locally prepared changes are accompanied by a recreated participant and roll back changes
+     * for those with no such participant.
+     */
+    public void endScan()
+    {
+        if (isFirst) {
+            // both AT and BA participants update state. so let the state manager know that the
+            // AT log records have all been scanned
+            RestaurantManager.getSingletonInstance().recoveryScanCompleted(ServiceStateManager.TX_TYPE_AT);
+            TheatreManager.getSingletonInstance().recoveryScanCompleted(ServiceStateManager.TX_TYPE_AT);
+            TaxiManager.getSingletonInstance().recoveryScanCompleted(ServiceStateManager.TX_TYPE_AT);
+            isFirst = false;
+        }
+    }
 }

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/recovery/DemoBARecoveryModule.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/recovery/DemoBARecoveryModule.java	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/recovery/DemoBARecoveryModule.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -1,5 +1,12 @@
 package com.jboss.jbosstm.xts.demo.services.recovery;
 
+import com.jboss.jbosstm.xts.demo.services.restaurant.RestaurantManager;
+import com.jboss.jbosstm.xts.demo.services.restaurant.RestaurantParticipantBA;
+import com.jboss.jbosstm.xts.demo.services.state.ServiceStateManager;
+import com.jboss.jbosstm.xts.demo.services.taxi.TaxiManager;
+import com.jboss.jbosstm.xts.demo.services.taxi.TaxiParticipantBA;
+import com.jboss.jbosstm.xts.demo.services.theatre.TheatreManager;
+import com.jboss.jbosstm.xts.demo.services.theatre.TheatreParticipantBA;
 import org.jboss.jbossts.xts.recovery.participant.ba.XTSBARecoveryManager;
 import org.jboss.jbossts.xts.recovery.participant.ba.XTSBARecoveryModule;
 import com.arjuna.wst.BusinessAgreementWithParticipantCompletionParticipant;
@@ -67,13 +74,26 @@
      */
     public BusinessAgreementWithParticipantCompletionParticipant deserializeParticipantCompletionParticipant(String id, ObjectInputStream stream) throws Exception
     {
-        if (id.startsWith("org.jboss.jbossts.xts-demo:restaurantBA") ||
-                id.startsWith("org.jboss.jbossts.xts-demo:theatreBA") ||
-                id.startsWith("org.jboss.jbossts.xts-demo:taxiBA")) {
-            System.out.println("xts-demo : attempting to deserialize WS-BA participant " + id);
-            BusinessAgreementWithParticipantCompletionParticipant participant = (BusinessAgreementWithParticipantCompletionParticipant)stream.readObject();
-            System.out.println("xts-demo : deserialized WS-BA participant " + id);
+        if (id.startsWith("org.jboss.jbossts.xts-demo:restaurantBA")) {
+            System.out.println("xts-demo : attempting to deserialize RestaurantParticipantBA " + id);
+            RestaurantParticipantBA participant = (RestaurantParticipantBA)stream.readObject();
+            // ensure that the participant has completed any changes to local service state.
+            System.out.println("xts-demo : deserialized RestaurantParticipantBA " + id);
+            RestaurantManager.getSingletonInstance().recovered(participant);
             return participant;
+        } else if (id.startsWith("org.jboss.jbossts.xts-demo:theatreBA")) {
+            System.out.println("xts-demo : attempting to deserialize TheatreParticipantBA " + id);
+            TheatreParticipantBA participant = (TheatreParticipantBA)stream.readObject();
+            // ensure that the participant has completed any changes to local service state.
+            System.out.println("xts-demo : deserialized TheatreParticipantBA " + id);
+            TheatreManager.getSingletonInstance().recovered(participant);
+            return participant;
+        } else if (id.startsWith("org.jboss.jbossts.xts-demo:taxiBA")) {
+            System.out.println("xts-demo : attempting to deserialize TaxiParticipantBA " + id);
+            TaxiParticipantBA participant = (TaxiParticipantBA)stream.readObject();
+            System.out.println("xts-demo : deserialized TaxiParticipantBA " + id);
+            TaxiManager.getSingletonInstance().recovered(participant);
+            return participant;
         }
         return null;
     }
@@ -150,4 +170,28 @@
         }
         return null;
     }
+
+
+    /**
+     * flags used to identify the first endScan call.
+     */
+    private boolean isFirst = true;
+
+    /**
+     * participant recovery modules may need to perform special processing when the a recovery scan has
+     * completed. in particular it is only after the first recovery scan has completed they can identify
+     * whether locally prepared changes are accompanied by a recreated participant and roll back changes
+     * for those with no such participant.
+     */
+    public void endScan()
+    {
+        if (isFirst) {
+            // both AT and BA participants update state. so let the state manager know that the
+            // BA log records have all been scanned
+            RestaurantManager.getSingletonInstance().recoveryScanCompleted(ServiceStateManager.TX_TYPE_BA);
+            TheatreManager.getSingletonInstance().recoveryScanCompleted(ServiceStateManager.TX_TYPE_BA);
+            TaxiManager.getSingletonInstance().recoveryScanCompleted(ServiceStateManager.TX_TYPE_BA);
+            isFirst = false;
+        }
+    }
 }
\ No newline at end of file

Added: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantConstants.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantConstants.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantConstants.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -0,0 +1,17 @@
+package com.jboss.jbosstm.xts.demo.services.restaurant;
+
+/**
+ * Constant values used by the restaurant service
+ */
+public class RestaurantConstants
+{
+    /**
+     * The default initial capacity of each seating area.
+     */
+    public static final int DEFAULT_SEATING_CAPACITY = 100;
+
+    public final static String STATE_FILENAME = "restaurantManagerState";
+
+    public final static String SHADOW_STATE_FILENAME = "restaurantManagerShadowState";
+
+}

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	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantManager.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -1,6 +1,6 @@
 /*
  * JBoss, Home of Professional Open Source
- * Copyright 2006, Red Hat Middleware LLC, and individual contributors
+ * Copyright 2005-2010, Red Hat, and individual contributors
  * as indicated by the @author tags. 
  * See the copyright.txt in the distribution for a full listing 
  * of individual contributors.
@@ -15,7 +15,6 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  * MA  02110-1301, USA.
  * 
- * (C) 2005-2006,
  * @author JBoss Inc.
  */
 /*
@@ -29,419 +28,270 @@
 
 package com.jboss.jbosstm.xts.demo.services.restaurant;
 
-import com.arjuna.wst.FaultedException;
+import com.jboss.jbosstm.xts.demo.services.state.ServiceStateManager;
+import static com.jboss.jbosstm.xts.demo.services.restaurant.RestaurantConstants.*;
 
-import java.util.Hashtable;
-import java.util.Enumeration;
+import javax.xml.ws.WebServiceException;
 import java.io.*;
 
 /**
  * The transactional application logic for the Restaurant Service.
  * <p/>
- * Stores and manages seating reservations. Knows nothing about Web Services.
- * Understands transactional booking lifecycle: unprepared, prepared, finished.
+ * Stores and manages seating reservations.
+ * <p/>
+ * The manager extends class ServiceStateManager which implements a very simple
+ * transactional resource manager. It gives the restaurant manager the ability to
+ * persist the web service state in a local disk file and to make transactional
+ * updates to that persistent state. The unit of locking is the whole of the
+ * service state so although bookings can be attempted by concurrent transactions
+ * only one such booking will commit, forcing other concurrent transactions to
+ * roll back. Conflict detection is implemented using a simple versioning scheme.
  *
- * </p>The manager maintains the following invariants regarding seating capacity:
- * <ul>
- * <li>nBooked == sum(unpreparedList.seatCount) + sum(preparedList.seatCount)
+ * The restaurant manager provides a book method allowing the web service endpoint to book
+ * or unbook seats. It also exposes prepare, commit and rollback operations used by both
+ * WSAT and WSBA participants to drive prepare, commit and rollback of changes to the
+ * persistent state. Finally it exposes recovery logic used by the WSAT and WSBA recovery
+ * modules to tie recovery of WSAT and WSBA participants to recovery and rollback of the
+ * local service state.
  *
- * <li>nPrepared = sum(prepared.seatCount)
- *
- * <li>nTotal == nFree + nPrepared + nCommitted
- * </ul>
- * Extended to include support for BA compensation based rollback
- * </p>
- * The manager now maintains an extra list compensatableList:
- *  <ul>
- * <li>nCompensatable == sum(compensatableList.seatCount)
- * </ul>
- * changes to nPrepared, nFree, nCommitted, nCompensatable, nTotal, preparedList and compensatableList are
- * always shadowed in persistent storage before returning control to clients.
- * </p>
- *
  * @author Jonathan Halliday (jonathan.halliday at arjuna.com)
- * @version $Revision: 1.3 $
+ * @author Andrew Dinn (adinn at redhat.com)
+ * @version $Revision:$
  */
-public class RestaurantManager implements Serializable
-{
-    /**
-     * Create and initialise a new RestaurantManager instance.
-     */
-    private RestaurantManager()
-    {
-        setToDefault(false);
-        // restore any state saved by a previous installation of this web service
-        restoreState();
-    }
+public class RestaurantManager extends ServiceStateManager<RestaurantState> {
 
+    /*****************************************************************************/
+    /* Support for the Web Service API                                           */
+    /*****************************************************************************/
+
     /**
-     * Book a number of seats in the restaurant.
+     * Accessor to obtain the singleton restaurant manager instance.
      *
-     * @param txID   The transaction identifier
-     * @param nSeats The number of seats requested
+     * @return the singleton RestaurantManager instance.
      */
-    public synchronized void bookSeats(Object txID, int nSeats)
+    public synchronized static RestaurantManager getSingletonInstance()
     {
-        // locate any pre-existing request for the same transaction
-        Integer request = (Integer) unpreparedTransactions.get(txID);
-        if (request == null)
+        if (singletonInstance == null)
         {
-            // this is the first request for this transaction
-            // setup the data structure to record it
-            request = new Integer(0);
+            singletonInstance = new RestaurantManager();
         }
 
-        // record the request, keyed to its transaction scope
-        request = new Integer(request.intValue() + nSeats);
-        unpreparedTransactions.put(txID, request);
-
-        // record the increased commitment to provide seating
-        nBookedSeats += nSeats;
-        // we don't actually need to update until prepare
+        return singletonInstance;
     }
 
     /**
-     * Attempt to ensure availability of the requested seating.
+     * Book a number of seats in the restaurant.
      *
-     * @param txID The transaction identifier
-     * @return true on success, false otherwise
+     * @param txID   The transaction identifier
+     * @param nSeats The number of seats requested
      */
-    public synchronized boolean prepareSeats(Object txID)
+    public synchronized void bookSeats(Object txID, int nSeats)
     {
-        // ensure that we have seen this transaction before
-        Integer request = (Integer) unpreparedTransactions.get(txID);
-        if (request == null)
-        {
-            return false; // error: transaction not registered
-        }
-        else
-        {
-            if (autoCommitMode)
-            {
-                if (request.intValue() <= nFreeSeats)
-                {
-                    // record the prepared transaction
-                    preparedTransactions.put(txID, request);
-                    unpreparedTransactions.remove(txID);
-                    // mark the prepared seats as unavailable
-                    nFreeSeats -= request.intValue();
-                    nPreparedSeats += request.intValue();
-                    updateState();
+        // we cannot proceed while a prepare is in progress
 
-                    return true;
-                }
-                else
-                {
-                    // we don't have enough seats available
-                    return false;
-                }
+        while (isLocked()) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                // ignore
             }
-            else
-            {
-                try
-                {
-                    // wait for a user commit/rollback decision
-                    isPreparationWaiting = true;
-                    synchronized (preparation)
-                    {
-                        preparation.wait();
-                    }
-                    isPreparationWaiting = false;
-
-                    // process the user decision
-                    if (isCommit)
-                    {
-                        // record the prepared transaction
-                        preparedTransactions.put(txID, request);
-                        unpreparedTransactions.remove(txID);
-                        // mark the prepared seats as unavailable
-                        nFreeSeats -= request.intValue();
-                        nPreparedSeats += request.intValue();
-                        updateState();
-                        return true;
-                    }
-                    else
-                    {
-                        return false;
-                    }
-                }
-                catch (Exception e)
-                {
-                    System.err.println("RestaurantManager.prepareSeats(): Unable to stop preparation.");
-                    return false;
-                }
-            }
         }
-    }
 
-    /**
-     * Release booked or prepared seats.
-     *
-     * @param txID The transaction identifier
-     * @return true on success, false otherwise
-     */
-    public synchronized boolean cancelSeats(Object txID)
-    {
-        boolean success = false;
-
-        // the transaction may be prepared, unprepared or unknown
-
-        if (preparedTransactions.containsKey(txID))
-        {
-            // undo the prepare operations
-            Integer request = (Integer) preparedTransactions.remove(txID);
-            nFreeSeats += request.intValue();
-            nPreparedSeats -= request.intValue();
-            nBookedSeats -= request.intValue();
-            updateState();
-            success = true;
+        if (restaurantState.freeSeats < nSeats ||
+                restaurantState.bookedSeats + nSeats > restaurantState.totalSeats) {
+            throw new WebServiceException("requested number of seats (" + nSeats + ") not available");
         }
-        else if (unpreparedTransactions.containsKey(txID))
-        {
-            // undo the booking operations
-            Integer request = (Integer) unpreparedTransactions.remove(txID);
-            nBookedSeats -= request.intValue();
-            success = true;
-        }
-        else
-        {
-            success = false; // error: transaction not registered
-        }
-        return success;
-    }
 
-    /**
-     * Compensate a committed booking.
-     *
-     * @param txID The transaction identifier
-     * @return true on success, false otherwise
-     */
-    public synchronized boolean compensateSeats(Object txID)
-            throws FaultedException
-    {
-        boolean success = false;
+        // create a state derived from the current state which reflects the new booking count
 
-        // the transaction must be compensatable
+        RestaurantState childState = restaurantState.derivedState();
 
-        if (compensatableTransactions.containsKey(txID))
-        {
-            // see if the user wants to report a compensation fault
-            
-            if (!autoCommitMode)
-            {
-                try
-                {
-                    // wait for a user commit/rollback decision
-                    isPreparationWaiting = true;
-                    synchronized (preparation)
-                    {
-                        preparation.wait();
-                    }
-                    isPreparationWaiting = false;
+        // update the number of booked and free seats in the derived state
 
-                    // process the user decision
-                    if (!isCommit)
-                    {
-                        throw new FaultedException("RestaurantManager.compensateSeats(): compensation fault");
-                    }
-                }
-                catch (Exception e)
-                {
-                    System.err.println("RestaurantManager.compensateSeats(): Unexpected error during compensation.");
-                    throw new FaultedException("RestaurantManager.compensateSeats(): compensation fault");
-                }
-            }
+        childState.freeSeats -= nSeats;
+        childState.bookedSeats += nSeats;
 
-            // compensate the committed transaction
-            Integer request = (Integer) compensatableTransactions.remove(txID);
-            nCompensatableSeats -= request.intValue();
+        // install this as the current transaction state
 
-            nCommittedSeats -= request.intValue();
-            nFreeSeats += request.intValue();
-            updateState();
-            success = true;
-        }
-        else
-        {
-            success = false; // error: transaction not registered
-        }
-        return success;
+        putState(txID, childState);
+
     }
 
     /**
-     * Commit seat bookings.
-     *
-     * @param txID The transaction identifier
-     * @return true on success, false otherwise
+     * check whether we have already seen a web service request in a given transaction
      */
-    public synchronized boolean commitSeats(Object txID)
+
+    public synchronized boolean knowsAbout(Object txID)
     {
-        return commitSeats(txID, false);
+        return getState(txID) != null;
     }
 
+    /*****************************************************************************/
+    /* Support for the AT and BA Participant API implementation                  */
+    /*****************************************************************************/
+
     /**
-     * Commit seat bookings, possibly allowing subsequent compensation.
+     * Prepare local state changes for the supplied transaction
      *
      * @param txID The transaction identifier
-     * @param compensatable true if it may be necessary to compensate this commit laer
      * @return true on success, false otherwise
      */
-    public synchronized boolean commitSeats(Object txID, boolean compensatable)
+    public boolean prepareSeats(Object txID)
     {
-        boolean success;
+        // ensure that we have seen this transaction before
+        RestaurantState childState = getState(txID);
+        if (childState == null) {
+            return false;
+        }
+        // we have a single monolithic state element which means that only one transaction can prepare
+        // at any given time. we lock this state at prepare by providing the txId as a locking id. it only
+        // gets unlocked when we reach commit or rollback. the equivalent to the lock in memory is the
+        // shadow state file on disk.
+        synchronized (this) {
+            while (isLocked()) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                    // ignore
+                }
+            }
 
-        // the transaction may be prepared, unprepared or unknown
+            // check no other bookings have been committed
 
-        if (preparedTransactions.containsKey(txID))
-        {
-            // complete the prepared transaction
-            Integer request = (Integer) preparedTransactions.remove(txID);
-            if (compensatable) {
-                nCompensatableSeats += request.intValue();
-                compensatableTransactions.put(txID, request);
+            if (!restaurantState.isParentOf(childState)) {
+                removeState(txID);
+                return false;
             }
-            nCommittedSeats += request.intValue();
-            nPreparedSeats -= request.intValue();
-            nBookedSeats -= request.intValue();
-            updateState();
-            success = true;
-        }
-        else if (unpreparedTransactions.containsKey(txID))
-        {
-            Integer request = (Integer) unpreparedTransactions.remove(txID);
-            boolean doCommit;
-            // check we have enough seats and if so
-            // use one phase commit optimisation, skipping prepare
 
-            if (autoCommitMode)
-            {
-                if (request.intValue() <= nFreeSeats)
-                {
-                    doCommit = true;
-                } else {
-                    doCommit = false;
-                }
-            }
-            else
-            {
-                try
-                {
-                    // wait for a user decision
-                    isPreparationWaiting = true;
-                    synchronized (preparation)
-                    {
+            // see if we need user confirmation
+
+            if (!autoCommitMode) {
+                // need to wait for the user to decide whether to go ahead or not with this participant
+                isPreparationWaiting = true;
+                synchronized (preparation) {
+                    try {
                         preparation.wait();
+                    } catch (InterruptedException e) {
+                        // ignore
                     }
-                    isPreparationWaiting = false;
+                }
+                isPreparationWaiting = false;
 
-                    // process the user decision
-                    doCommit = isCommit;
-                } catch (Exception e) {
-                    System.err.println("RestaurantManager.commitSeats(): Unable to perform commit.");
-                    doCommit = false;
+                // process the user decision
+                if (!isCommit) {
+                    removeState(txID);
+                    return false;
                 }
             }
 
-            if (doCommit) {
-                if (compensatable) {
-                    nCompensatableSeats += request.intValue();
-                    compensatableTransactions.put(txID, request);
-                }
-                nCommittedSeats += request.intValue();
-                nFreeSeats -= request.intValue();
-                nBookedSeats -= request.intValue();
-                updateState();
-                success = true;
-            } else {
-                // get rid of the commitment to keep these seats
-                nBookedSeats -= request.intValue();
-                success = false;
+            // ok, so lock the state against other prepare/commits
+
+            lock(txID);
+        }
+        // if we got here then no other changes have invalidated our booking and we have locked out
+        // further changes until commit or rollback occurs. we write the derived child state to the
+        // shadow state file before returning. if we crash after the write we will detect the shadow
+        // state at reboot and restore the lock.
+        try {
+            writeShadowState(txID, childState);
+            return true;
+        } catch (Exception e) {
+            clearShadowState(txID);
+            synchronized (this) {
+                removeState(txID);
+                unlock();
             }
+            System.err.println("RestaurantManager.prepareSeats(): Error attempting to prepare transaction: " + e);
+            return false;
         }
-        else
-        {
-            success = false; // error: transaction not registered
-        }
-
-        return success;
     }
 
     /**
-     * Close seat bookings, removing possibility for compensation.
+     * commit local state changes for the supplied transaction
      *
-     * @param txID The transaction identifier
-     * @return true on success, false otherwise
+     * @param txID
      */
-    public synchronized boolean closeSeats(Object txID)
+    public void commitSeats(Object txID)
     {
-        boolean success;
-
-        // the transaction may be compensatable or unknown
-
-        if (compensatableTransactions.containsKey(txID))
-        {
-            // complete the prepared transaction
-            Integer request = (Integer) compensatableTransactions.remove(txID);
-
-            nCompensatableSeats -= request.intValue();
-            updateState();
-            success = true;
+        synchronized (this) {
+            // if there is a shadow state with this id then we need to copy the shadow state file over to the
+            // real state file. it may be that there is no shadow state because this is a repeated commit
+            // request. if so then we must have committed earlier so there is no harm done.
+            if (isLockID(txID)) {
+                commitShadowState(txID);
+                // update the current state with the prepared state.
+                restaurantState = getPreparedState();
+                unlock();
+            }
+            removeState(txID);
         }
-        else
-        {
-            success = false; // error: transaction not registered for compensation
-        }
-
-        return success;
     }
 
     /**
-     * Handle BA error for a specific booking.
-     *
-     * @param txID The transaction identifier
+     * roll back local state changes for the supplied transaction
+     * @param txID
      */
-    public synchronized void error(Object txID)
+    public void rollbackSeats(Object txID)
     {
-        // undo any provisional or actual changes associated wiht the booking
-
-        Integer request = (Integer) unpreparedTransactions.remove(txID);
-        if (request != null) {
-            nBookedSeats -= request.intValue();
-        } else if ((request = (Integer)preparedTransactions.remove(txID)) != null) {
-            nFreeSeats += request.intValue();
-            nPreparedSeats -= request.intValue();
-            nBookedSeats -= request.intValue();
-            updateState();
-        } else if ((request = (Integer)compensatableTransactions.remove(txID)) != null) {
-            nCompensatableSeats -= request.intValue();
-
-            nCommittedSeats -= request.intValue();
-            nFreeSeats += request.intValue();
-            updateState();
+        synchronized (this) {
+            removeState(txID);
+            if (isLockID(txID)) {
+                clearShadowState(txID);
+                unlock();
+            }
         }
     }
 
     /**
-     * Determine if a specific transaction is known to the business logic.
-     *
-     * @param txID The uniq id for the transaction
-     * @return true if the business logic is holding state related to the given txID,
-     *         false otherwise.
+     * handle a recovery error by rolling back the changes associated with the transaction
+     * @param txID
      */
-    public boolean knowsAbout(Object txID)
+    public void error(String txID)
     {
-        return (unpreparedTransactions.containsKey(txID) || preparedTransactions.containsKey(txID));
+        rollbackSeats(txID);
     }
 
+    /*****************************************************************************/
+    /* Accessors for the GUI to view and reset the service state                 */
+    /*****************************************************************************/
+
     /**
-     * Change the capacity of the Resaurant.
-     *
-     * @param nSeats The new capacity
+     * Reset to the initial state.
      */
-    public void newCapacity(int nSeats)
+    public synchronized void reset()
     {
-        nFreeSeats += nSeats - nTotalSeats;
-        nTotalSeats = nSeats;
+        // we cannot proceed while a prepare is in progress
+
+        while (isLocked()) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
+
+        // undo all existing bookings
+
+        RestaurantState resetState = restaurantState.derivedState();
+
+        resetState.totalSeats = DEFAULT_SEATING_CAPACITY;
+        resetState.bookedSeats = 0;
+        resetState.freeSeats = resetState.totalSeats;
+
+        Object txId = "reset-transaction";
+        try {
+            writeShadowState(txId, resetState);
+            commitShadowState(txId);
+        } catch (IOException e) {
+            clearShadowState(txId);
+             System.out.println("error : unable to reset restaurant manager state " + e);
+        }
+
+        restaurantState = resetState;
+        
+        // remove any in-progress transactions
+
+        clearTransactions();
     }
 
     /**
@@ -451,7 +301,7 @@
      */
     public int getNFreeSeats()
     {
-        return nFreeSeats;
+        return restaurantState.freeSeats;
     }
 
     /**
@@ -461,7 +311,7 @@
      */
     public int getNTotalSeats()
     {
-        return nTotalSeats;
+        return restaurantState.totalSeats;
     }
 
     /**
@@ -471,40 +321,25 @@
      */
     public int getNBookedSeats()
     {
-        return nBookedSeats;
+        return restaurantState.bookedSeats;
     }
 
     /**
-     * Get the number of prepared seats.
+     * Get the number of prepared seats in the given area.
      *
-     * @return The number of prepared seats
+     * @return The number of booked seats
      */
-    public int getNPreparedSeats()
+    public synchronized int getNPreparedSeats()
     {
-        return nPreparedSeats;
+        if (isLocked()) {
+            RestaurantState childState = getPreparedState();
+            return childState.bookedSeats - restaurantState.bookedSeats;
+        } else {
+            return 0;
+        }
     }
 
     /**
-     * Get the number of committed seats in the given area.
-     *
-     * @return The number of committed seats
-     */
-    public int getNCommittedSeats()
-    {
-        return nCommittedSeats;
-    }
-
-    /**
-     * Get the number of compensatable seats in the given area.
-     *
-     * @return The number of compensatable seats
-     */
-    public int getNCompensatableSeats()
-    {
-        return nCompensatableSeats;
-    }
-
-    /**
      * Determine the autoCommit status of the instance.
      *
      * @return true if autoCommit mode is active, false otherwise
@@ -564,93 +399,75 @@
         isCommit = commit;
     }
 
+    /*****************************************************************************/
+    /* Implementation of inherited abstract sate management API                  */
+    /*****************************************************************************/
+
     /**
-     * (re-)initialise the instance data structures deleting any previously saved
-     * transaction state.
+     * identify the name of file used to store the current service state
+      * @return the name of the file used to store the current service state
      */
-    public void setToDefault()
-    {
-        setToDefault(true);
+    @Override
+    public String getStateFilename() {
+        return STATE_FILENAME;
     }
 
     /**
-     * (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
+     * identify the name of file used to store the shadow service state
+      * @return the name of the file used to store the shadow service state
      */
-    public void setToDefault(boolean deleteSavedState)
-    {
-        nTotalSeats = DEFAULT_SEATING_CAPACITY;
-        nFreeSeats = nTotalSeats;
-        nBookedSeats = 0;
-        nPreparedSeats = 0;
-        nCommittedSeats = 0;
-        nCompensatableSeats = 0;
-        compensatableTransactions = new Hashtable();
-        preparedTransactions = new Hashtable();
-        unpreparedTransactions = new Hashtable();
-        autoCommitMode = true;
-        preparation = new Object();
-        isPreparationWaiting = false;
-        isCommit = true;
-        if (deleteSavedState) {
-            // just write the current state.
-            updateState();
-        }
+    @Override
+    public String getShadowStateFilename() {
+        return SHADOW_STATE_FILENAME;
     }
 
+    /*****************************************************************************/
+    /* Recovery methods maintaining consistency of local and  WSAT/WSBA state    */
+    /*****************************************************************************/
+
     /**
-     * Allow use of a singleton model for web services demo.
-     *
-     * @return the singleton RestaurantManager instance.
+     * called by the AT recovery module when an AT participant is recovered from a log record
      */
-    public synchronized static RestaurantManager getSingletonInstance()
+    public void recovered(RestaurantParticipantAT participant)
     {
-        if (singletonInstance == null)
-        {
-            singletonInstance = new RestaurantManager();
+        // if this AT participant matches the prepared TX id then we need to leave it prepared and locked
+        // at the end of scanning so it can be completed at commit time
+        if (isLockID(participant.txID)) {
+            rollbackPreparedTx = false;
         }
-
-        return singletonInstance;
     }
 
     /**
-     * A singleton instance of this class.
+     * called by the BA recovery module when an AT participant is recovered from a log record
      */
-    private static RestaurantManager singletonInstance;
+    public void recovered(RestaurantParticipantBA participant)
+    {
+        // if this AT participant matches the prepared TX id then we roll it forward here by calling
+        // confirmCompleted so once again we don't need to roll back the prepared state
+        if (isLockID(participant.txID)) {
+            participant.confirmCompleted(true);
+            rollbackPreparedTx = false;
+        }
+    }
 
-    /**
-     * The total seating capacity.
-     */
-    private int nTotalSeats;
+    public void recoveryScanCompleted(int txType)
+    {
+        super.recoveryScanCompleted(txType);
+        if (completedScans == TX_TYPE_BOTH && rollbackPreparedTx) {
+            rollbackSeats(getLockID());
+        }
+    }
 
-    /**
-     * The number of free seats.
-     */
-    private int nFreeSeats;
+    /*****************************************************************************/
+    /* Private implementation                                                    */
+    /*****************************************************************************/
 
     /**
-     * The number of booked seats.
-     * <p/>
-     * Note: This may exceed the total seating capacity
+     * The singleton instance of this class.
      */
-    private int nBookedSeats;
+    private static RestaurantManager singletonInstance;
 
     /**
-     * The number of prepared (promised) seats.
-     */
-    private int nPreparedSeats;
-    /**
-     * The number of committed seats in each area.
-     */
-    private int nCommittedSeats;
-
-    /**
-     * The number of compensatable seats in each area.
-     */
-    private int nCompensatableSeats;
-
-    /**
      * The auto commit mode.
      * <p/>
      * true = automatically commit, false = manually commit
@@ -673,161 +490,54 @@
     private boolean isCommit;
 
     /**
-     * The transactions we know about but which have not been prepared.
+     * Flag which determines whether we have to roll back any prepared changes to the server. We roll back
+     * changes for an AT or BA participant if there is no associated log record because the participant never
+     * prepared or completed, respectively. If we see a log record for an AT participant we leave the prepared
+     * state behind since the AT participant has prepared and may still commit.
      */
-    private Hashtable unpreparedTransactions;
+    private boolean rollbackPreparedTx;
 
     /**
-     * The transactions we know about and are prepared to commit.
+     * the latest version of the restaurant state which includes a version id.
+     * this state object is always stored on disk in the restaurant state file
+     * a prepared version of a single derived child state may also exist on disk
+     * in the restaurant shadow state file.
      */
-    private Hashtable preparedTransactions;
+    private RestaurantState restaurantState;
 
     /**
-     * The transactions we know about and are prepared to compensate.
+     * Create and initialise a new RestaurantManager instance either restoring any
+     * existing service state from disk or else installing and committing to disk
+     * a new initial state. If a prepared version of a derived child state (shadow state)
+     * is found on disk then the shadow state  is also loaded and the current state is
+     * locked awaiting recovery. recovery will either roll forward the shadow state, using
+     * it to replace the current state or roll it back.
      */
-    private Hashtable compensatableTransactions;
-
-    /**
-     * 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 = "restaurantManagerState";
-
-    /**
-     * the name of the file used 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()
+    private RestaurantManager()
     {
-        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()) {
+        RestaurantState restoredState = restoreState();
+        if (restoredState == null) {
+            // we need to create a new initial state and persist it to disk
+            restoredState = RestaurantState.initialState();
+            Object txId = "initialisation-transaction-" + System.currentTimeMillis();
             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);
+                writeShadowState(txId, restoredState);
+                commitShadowState(txId);
+            } catch (IOException e) {
+                clearShadowState(txId);
+                System.out.println("error : unable to initialise 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);
+        restaurantState = restoredState;
+        
+        preparation = new Object();
+        // we will roll back any locally prepared changes to web service state unless we discover that
+        // they are needed during recovery
+        rollbackPreparedTx = isLocked();
 
-        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);
+        isCommit = true;
+        autoCommitMode = true;
+        isPreparationWaiting = false;
     }
-
-    /**
-     * 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();
-        nCompensatableSeats = ois.readInt();
-        compensatableTransactions = new Hashtable();
-        String name = (String)ois.readObject();
-        while (!"".equals(name)) {
-            int count = ois.readInt();
-            compensatableTransactions.put(name, new Integer(count));
-            name = (String)ois.readObject();
-        }
-        preparedTransactions = new Hashtable();
-        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);
-        oos.writeInt(nCompensatableSeats);
-        Enumeration keys = compensatableTransactions.keys();
-        while (keys.hasMoreElements()) {
-            String name = (String)keys.nextElement();
-            int count = ((Integer)compensatableTransactions.get(name)).intValue();
-            oos.writeObject(name);
-            oos.writeInt(count);
-        }
-        oos.writeObject("");
-        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	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantParticipantAT.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -32,7 +32,6 @@
 import com.arjuna.wst.*;
 
 import java.io.Serializable;
-import java.io.IOException;
 
 /**
  * An adapter class that exposes the RestaurantManager transaction lifecycle
@@ -44,6 +43,9 @@
  */
 public class RestaurantParticipantAT implements Durable2PCParticipant, Serializable
 {
+    /************************************************************************/
+    /* public methods                                                       */
+    /************************************************************************/
     /**
      * Participant instances are related to transaction instances
      * in a one to one manner.
@@ -57,6 +59,9 @@
         this.txID = txID;
     }
 
+    /************************************************************************/
+    /* Durable2PCParticipant methods                                        */
+    /************************************************************************/
     /**
      * Invokes the prepare step of the business logic,
      * reporting activity and outcome.
@@ -87,7 +92,6 @@
         }
         else
         {
-            getRestaurantManager().cancelSeats(txID) ;
             getRestaurantView().addMessage("Prepare failed (not enough seats?) Returning 'Aborted'\n");
             getRestaurantView().updateFields();
             return new Aborted();
@@ -110,18 +114,11 @@
 
         getRestaurantView().addMessage("id:" + txID + ". Commit called on participant: " + this.getClass().toString());
 
-        boolean success = getRestaurantManager().commitSeats(txID);
+        getRestaurantManager().commitSeats(txID);
 
         // Log the outcome
 
-        if (success)
-        {
-            getRestaurantView().addMessage("Seats committed\n");
-        }
-        else
-        {
-            getRestaurantView().addMessage("Something went wrong (Transaction not registered?)\n");
-        }
+        getRestaurantView().addMessage("Seats committed\n");
 
         getRestaurantView().updateFields();
     }
@@ -142,35 +139,13 @@
 
         getRestaurantView().addMessage("id:" + txID + ". Rollback called on participant: " + this.getClass().toString());
 
-        boolean success = getRestaurantManager().cancelSeats(txID);
+        getRestaurantManager().rollbackSeats(txID);
 
-        // Log the outcome
-
-        if (success)
-        {
-            getRestaurantView().addMessage("Seats booking cancelled\n");
-        }
-        else
-        {
-            getRestaurantView().addMessage("Something went wrong (Transaction not registered?)\n");
-        }
-
+        getRestaurantView().addMessage("Seats booking cancelled\n");
+        
         getRestaurantView().updateFields();
     }
 
-    /**
-     * Shortcut method which combines the prepare
-     * and commit steps in a single operation.
-     *
-     * @throws WrongStateException
-     * @throws SystemException
-     */
-    public void commitOnePhase() throws WrongStateException, SystemException
-    {
-        prepare();
-        commit();
-    }
-
     public void unknown() throws SystemException
     {
         // used for calbacks during crash recovery. This impl is not recoverable
@@ -181,6 +156,9 @@
         // used for calbacks during crash recovery. This impl is not recoverable
     }
 
+    /************************************************************************/
+    /* private implementation                                               */
+    /************************************************************************/
     /**
      * Id for the transaction which this participant instance relates to.
      * Set by the service (via contrtuctor) at enrolment time, this value
@@ -196,4 +174,3 @@
         return RestaurantManager.getSingletonInstance();
     }
 }
-

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantParticipantBA.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantParticipantBA.java	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantParticipantBA.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -30,6 +30,7 @@
 package com.jboss.jbosstm.xts.demo.services.restaurant;
 
 import com.arjuna.wst.*;
+import com.arjuna.wst11.ConfirmCompletedParticipant;
 
 import java.io.Serializable;
 
@@ -41,8 +42,14 @@
  * @author Jonathan Halliday (jonathan.halliday at arjuna.com)
  * @version $Revision: 1.3 $
  */
-public class RestaurantParticipantBA implements BusinessAgreementWithParticipantCompletionParticipant, Serializable
+public class RestaurantParticipantBA
+        implements BusinessAgreementWithParticipantCompletionParticipant,
+        ConfirmCompletedParticipant,
+        Serializable
 {
+    /************************************************************************/
+    /* public methods                                                       */
+    /************************************************************************/
     /**
      * Participant instances are related to business method calls
      * in a one to one manner.
@@ -54,10 +61,12 @@
     {
         // we need to save the txID for later use when logging.
         this.txID = txID;
-        // we also need the business paramater(s) in case of compensation
         this.seatCount = how_many;
     }
 
+    /************************************************************************/
+    /* BusinessAgreementWithParticipantCompletionParticipant methods        */
+    /************************************************************************/
     /**
      * The transaction has completed successfully. The participant previously
      * informed the coordinator that it was ready to complete.
@@ -68,17 +77,10 @@
 
     public void close() throws WrongStateException, SystemException
     {
-        // let the manager know that this activity no longer requires the option of compensation
+        // nothing to do here as the seats are already booked
 
         System.out.println("RestaurantParticipantBA.close");
 
-        if (!getRestaurantManager().closeSeats(txID)) {
-            // throw a WrongStateException to indicate that we were not expecting a close
-            System.out.println("RestaurantParticipantBA.close : not expecting a close for BA participant " + txID);
-
-            throw new WrongStateException("Unexpected close for BA participant " + txID);
-        }
-
         getRestaurantView().addMessage("id:" + txID + ". Close called on participant: " + this.getClass());
 
         getRestaurantView().updateFields();
@@ -100,14 +102,10 @@
 
         System.out.println("RestaurantParticipantBA.cancel");
 
-        if (!getRestaurantManager().cancelSeats(txID)) {
-            // throw a WrongStateException to indicate that we were not expecting a close
-            System.out.println("RestaurantParticipantBA.cancel : not expecting a cancel for BA participant " + txID);
+        getRestaurantManager().rollbackSeats(txID);
+        
+        getRestaurantView().addMessage("id:" + txID + ". Cancel called on participant: " + this.getClass().toString());
 
-            throw new WrongStateException("Unexpected cancel for BA participant " + txID);
-        }
-
-        getRestaurantView().addMessage("id:" + txID + ". Cancel called on participant: " + this.getClass().toString());
         getRestaurantView().updateFields();
     }
 
@@ -128,22 +126,20 @@
 
         getRestaurantView().updateFields();
 
-        // tell the manager to compensate
+        // we perform the compensation by preparing and then committing a change which
+        // decrements the bookings
 
-        try {
-            if (!getRestaurantManager().compensateSeats(txID)) {
-                // throw a WrongStateException to indicate that we were not expecting a close
-                System.out.println("RestaurantParticipantBA.compensate : not expecting a compensate for BA participant " + txID);
+        String compensationTxID = txID + "-compensation";
 
-                throw new WrongStateException("Unexpected compensate for BA participant " + txID);
-            }
-        } catch (FaultedException fe) {
-            getRestaurantView().addMessage("id:" + txID + ". FaultedException when compensating participant: " + this.getClass().toString());
+        getRestaurantManager().bookSeats(compensationTxID, -seatCount);
+        
+        if (!getRestaurantManager().prepareSeats(compensationTxID)) {
+            getRestaurantView().addMessage("id:" + txID + ". Failed to compensate participant: " + this.getClass().toString());
 
-            getRestaurantView().updateFields();
-            throw fe;
+            throw new FaultedException("Failed to compensate participant: " + this.getClass().toString());
         }
-
+        getRestaurantManager().commitSeats(compensationTxID);
+        
         getRestaurantView().addMessage("id:" + txID + ". Compensated participant: " + this.getClass().toString());
 
         getRestaurantView().updateFields();
@@ -175,9 +171,32 @@
         getRestaurantView().updateFields();
     }
 
+    /************************************************************************/
+    /* ConfirmCompletedParticipant methods                                  */
+    /************************************************************************/
+
     /**
+     * method called to perform commit or rollback of prepared changes to the underlying manager state after
+     * the participant recovery record has been written
+     *
+     * @param confirmed true if the log record has been written and changes should be rolled forward and false
+     * if it has not been written and changes should be rolled back
+     */
+
+    public void confirmCompleted(boolean confirmed) {
+        if (confirmed) {
+            getRestaurantManager().commitSeats(txID);
+        } else {
+            getRestaurantManager().rollbackSeats(txID);
+        }
+    }
+
+    /************************************************************************/
+    /* private implementation                                               */
+    /************************************************************************/
+    /**
      * Id for the transaction which this participant instance relates to.
-     * Set by the service (via contrtuctor) at enrolment time, this value
+     * Set by the service (via constructor) at enrolment time, this value
      * is used in informational log messages.
      */
     protected String txID;
@@ -185,13 +204,13 @@
     /**
      * Copy of business state information, may be needed during compensation.
      */
-    protected int seatCount;
+    private int seatCount;
 
-    public RestaurantView getRestaurantView() {
+    private RestaurantView getRestaurantView() {
         return RestaurantView.getSingletonInstance();
     }
 
-    public RestaurantManager getRestaurantManager() {
+    private 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	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantServiceAT.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -60,26 +60,6 @@
 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.
@@ -120,6 +100,7 @@
 
         restaurantView.addMessage("id:" + transactionId + ". Received a booking request for one table of " + how_many + " people");
 
+        // invoke the backend business logic:
         restaurantManager.bookSeats(transactionId, how_many);
 
         restaurantView.addMessage("Request complete\n");

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantServiceBA.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantServiceBA.java	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantServiceBA.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -93,55 +93,77 @@
         restaurantView.addPrepareMessage("id:" + transactionId + ". Received a booking request for one table of " + how_many + " people");
         restaurantView.updateFields();
 
+        if (restaurantManager.knowsAbout(transactionId)) {
+            // hmm, this means we have already completed changes in this transaction and are awaiting a close
+            //or compensate request. this service does not support repeated requests in the same activity so
+            // we fail this request.
+
+            restaurantView.addMessage("id:" + transactionId + ". Participant already enrolled!");
+            restaurantView.updateFields();
+            System.err.println("bookSeats: request failed");
+            return false;
+        }
+
+        RestaurantParticipantBA restaurantParticipant = new RestaurantParticipantBA(transactionId, how_many);
+
+        BAParticipantManager participantManager;
+
+        // enlist the Participant for this service:
+        try
+        {
+            participantManager = activityManager.enlistForBusinessAgreementWithParticipantCompletion(restaurantParticipant, "org.jboss.jbossts.xts-demo:restaurantBA:" + new Uid().toString());
+        }
+        catch (Exception e)
+        {
+            restaurantView.addMessage("id:" + transactionId + ". Participant enrolement failed");
+            restaurantManager.rollbackSeats(transactionId);
+            System.err.println("bookSeats: Participant enlistment failed");
+            e.printStackTrace(System.err);
+            return false;
+        }
+
         // invoke the backend business logic:
         restaurantManager.bookSeats(transactionId, how_many);
 
-        // attempt to finalise the booking
-        // (it will be compensated later if necessary)
+        // this service employs the participant completion protocol which means it decides when it wants to
+        // commit local changes. so we prepare and commit those changes now. if any other participant fails
+        // or the client decides to cancel we can rely upon being told to compensate.
+
         if (restaurantManager.prepareSeats(transactionId))
         {
             restaurantView.addMessage("id:" + transactionId + ". Seats prepared, trying to commit and enlist compensation Participant");
             restaurantView.updateFields();
 
-            // it worked, so now we need a participant enlisted in case of compensation:
-
-            RestaurantParticipantBA restaurantParticipant = new RestaurantParticipantBA(transactionId, how_many);
-            // enlist the Participant for this service:
-            BAParticipantManager participantManager = null;
             try
             {
-                participantManager = activityManager.enlistForBusinessAgreementWithParticipantCompletion(restaurantParticipant, "org.jboss.jbossts.xts-demo:restaurantBA:" + new Uid().toString());
+                // tell the participant manager we have finished our work
+                // this will call back to the participant once a compensation recovery record has been written
+                // allowing it to commit or roll back the restaurant manager
+                participantManager.completed();
             }
             catch (Exception e)
             {
-                restaurantView.addMessage("id:" + transactionId + ". Participant enrolement failed");
-                restaurantManager.cancelSeats(transactionId);
-                System.err.println("bookSeats: Participant enlistment failed");
+                System.err.println("bookSeats: 'completed' callback failed");
+                restaurantManager.rollbackSeats(transactionId);
                 e.printStackTrace(System.err);
                 return false;
             }
-
-            // finish the booking in the backend ensuring it is compensatable:
-            restaurantManager.commitSeats(transactionId, true);
-
+        }
+        else
+        {
+            restaurantView.addMessage("id:" + transactionId + ". Failed to reserve seats.");
+            restaurantView.updateFields();
             try
             {
-                // tell the manager we have finished our work:
-                participantManager.completed();
+                // tell the participant manager we cannot complete. this will force the activity to fail
+                participantManager.cannotComplete();
             }
             catch (Exception e)
             {
-                System.err.println("bookSeats: 'completed' callback failed");
-                restaurantManager.cancelSeats(transactionId);
+                System.err.println("bookSeats: 'cannotComplete' callback failed");
                 e.printStackTrace(System.err);
                 return false;
             }
-        }
-        else
-        {
-            restaurantView.addMessage("id:" + transactionId + ". Failed to reserve seats. Cancelling.");
-            restaurantManager.cancelSeats(transactionId);
-            restaurantView.updateFields();
             return false;
         }
 

Added: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantState.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantState.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantState.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -0,0 +1,75 @@
+package com.jboss.jbosstm.xts.demo.services.restaurant;
+
+import com.jboss.jbosstm.xts.demo.services.state.ServiceState;
+import static com.jboss.jbosstm.xts.demo.services.restaurant.RestaurantConstants.*;
+
+/**
+ * An object which models the state of a restaurant identifying the number of free and
+ * booked seats and the total available
+ */
+public class RestaurantState extends ServiceState {
+    int totalSeats;
+    int bookedSeats;
+    int freeSeats;
+
+    /**
+     * create a new initial restaurant state
+     * @return
+     */
+    public static RestaurantState initialState()
+    {
+        return new RestaurantState();
+    }
+
+    /**
+     * derive a child restaurant state from this state
+     * @return
+     */
+    public RestaurantState derivedState()
+    {
+        return new RestaurantState(this);
+    }
+
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append("RestaurantState{version=");
+        builder.append(version);
+        builder.append(", totalSeats=");
+        builder.append(totalSeats);
+        builder.append(", bookedSeats=");
+        builder.append(bookedSeats);
+        builder.append(", freeSeats=");
+        builder.append(freeSeats);
+        builder.append("}");
+        return builder.toString();
+    }
+    /**
+     * create a new initial restaurant state
+     *
+     * @param totalSeats
+     */
+    private RestaurantState()
+    {
+        super();
+        this.totalSeats = DEFAULT_SEATING_CAPACITY;
+        this.bookedSeats = 0;
+        this.freeSeats = DEFAULT_SEATING_CAPACITY;
+    }
+
+    /**
+     * create a derived restaurant state with a given number of bookings and a specific version
+     *
+     * @param totalSeats
+     * @param bookedSeats
+     * @param version
+     */
+    private RestaurantState(RestaurantState parent)
+    {
+        super(parent);
+        this.totalSeats = parent.totalSeats;
+        this.bookedSeats = parent.bookedSeats;
+        this.freeSeats = totalSeats - bookedSeats;
+    }
+
+}

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	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/restaurant/RestaurantView.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -123,7 +123,7 @@
         jLabel2.setText("Prepared,    ");
         jPanel2.add(jLabel2);
 
-        jLabelNConfirmedSeats.setText(Integer.toString(restManager.getNCommittedSeats()));
+        jLabelNConfirmedSeats.setText(Integer.toString(0));
         jLabelNConfirmedSeats.setForeground(new java.awt.Color(0, 51, 204));
         jLabelNConfirmedSeats.setFont(new java.awt.Font("Dialog", 1, 18));
         jPanel2.add(jLabelNConfirmedSeats);
@@ -263,7 +263,7 @@
      */
     private void jButtonResetFieldsActionPerformed(java.awt.event.ActionEvent evt)
     {//GEN-FIRST:event_jButtonResetFieldsActionPerformed
-        restManager.setToDefault();
+        restManager.reset();
         updateFields();
     }//GEN-LAST:event_jButtonResetFieldsActionPerformed
 
@@ -336,7 +336,7 @@
     {//GEN-FIRST:event_jButtonSetNTotalSeatsActionPerformed
         String strNSeats = jTextFieldNewNTotalSeats.getText();
 
-        restManager.newCapacity(Integer.parseInt(strNSeats));
+        restManager.reset();
         int nFreeSeats = restManager.getNFreeSeats();
 
         jLabelNTotalSeats.setText(strNSeats);
@@ -386,7 +386,7 @@
         jLabelNTotalSeats.setText(Integer.toString(restManager.getNTotalSeats()));
         jTextFieldNewNTotalSeats.setText(Integer.toString(restManager.getNTotalSeats()));
         jLabelNPreparedSeats.setText(Integer.toString(restManager.getNPreparedSeats()));
-        jLabelNConfirmedSeats.setText(Integer.toString(restManager.getNCommittedSeats()));
+        jLabelNConfirmedSeats.setText(Integer.toString(0));
         jLabelNFreeSeats.setText(Integer.toString(restManager.getNFreeSeats()));
         jLabelNBookedSeats.setText(Integer.toString(restManager.getNBookedSeats()));
 

Added: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/state/ServiceState.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/state/ServiceState.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/state/ServiceState.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -0,0 +1,43 @@
+package com.jboss.jbosstm.xts.demo.services.state;
+
+import java.io.Serializable;
+
+/**
+ * A class which provides a simple versioning capability based on version numbers. A derived
+ * child state has a version number one greater than its parent state.
+ *
+ * Services maintain a current service state plus an indexed set of per-transaction derived child states.
+ * Conflicts can be detected at prepare time by comparing the derived state for the preparing transaction
+ * with the current state. If the derived state is not a child of the current state then an intervening
+ * write has occurred. If it is a child state of the current state then there can be no write conflict
+ * and the prepare may proceed.
+ */
+public class ServiceState implements Serializable {
+    protected long version;
+
+    /**
+     * construct an initial state with an initial version number of 0
+     */
+    protected ServiceState() {
+        this.version = 0;
+    }
+
+    /**
+     * construct a new state from an existing state bumping up the version number by one
+      * @param parent
+     */
+    public ServiceState(ServiceState parent) {
+        this.version = parent.version + 1;
+    }
+
+    /**
+     * test whether the child state was immediately derived from this state by checking the version numbers
+     *
+     * @param child the child state to be tested
+     * @return true if the child state was immediately derived from this state otherwise false
+     */
+    public boolean isParentOf(ServiceState child)
+    {
+        return (child.version == (version + 1));
+    }
+}

Added: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/state/ServiceStateManager.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/state/ServiceStateManager.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/state/ServiceStateManager.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -0,0 +1,288 @@
+package com.jboss.jbosstm.xts.demo.services.state;
+
+import java.io.*;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+/**
+ * An abstract class extended by the web service manager classes which provides a simple capability
+ * for persistent state management.
+ *
+ * The web services need to maintain a copy of the current service state on the local disk. They also need
+ * to be able to update the service state by first persisting a copy of the new state and then either
+ * using it to replace the current version (commit) or throwing it away. In either case the updates
+ * have to be done under the control of the web service transaction coordinator. Effectively, a service
+ * manager derived from this class has the ability to operate like a simple transactional resource.
+ */
+public abstract class ServiceStateManager<T extends ServiceState> {
+    protected ServiceStateManager()
+    {
+        transactions = new Hashtable<Object, T>();
+    }
+
+    protected void putState(Object txId, T state)
+    {
+        transactions.put(txId, state);
+    }
+
+    protected T getState(Object txId)
+    {
+        return transactions.get(txId);
+    }
+
+    protected void removeState(Object txId)
+    {
+        transactions.remove(txId);
+    }
+
+    protected boolean isLocked()
+    {
+        return (preparedTxID != null);
+    }
+
+    protected Object getLockID()
+    {
+        return preparedTxID;
+    }
+
+    protected boolean isLockID(Object txId)
+    {
+        if (preparedTxID != null) {
+            return preparedTxID.equals(txId);
+        } else {
+            return txId == null;
+        }
+    }
+
+    protected void lock(Object preparedTxID)
+    {
+        this.preparedTxID = preparedTxID;
+    }
+
+    protected void unlock()
+    {
+        this.preparedTxID = null;
+        this.notifyAll();
+    }
+
+    protected T getPreparedState()
+    {
+        if (preparedTxID != null) {
+            return transactions.get(preparedTxID);
+        }
+        
+        return null;
+    }
+    /**
+     * persist the prepared state for a transaction including the transaction id and the derived state
+     * containing the modified booking information
+     * @param txId
+     * @param childState
+     * @throws java.io.IOException
+     */
+    public void writeShadowState(Object txId, T childState) throws IOException
+    {
+        FileOutputStream fos = null;
+        ObjectOutputStream oos = null;
+        try {
+            fos = new FileOutputStream(getShadowStateFilename());
+            oos = new ObjectOutputStream(fos);
+            oos.writeObject(txId);
+            oos.writeObject(childState);
+        } finally {
+            if (oos != null) {
+                oos.close();
+            } else if (fos != null) {
+                fos.close();
+            }
+        }
+    }
+
+    /**
+     * delete any persisted prepared state
+     */
+    public void clearShadowState(Object txId)
+    {
+        File shadowFile = new File(getShadowStateFilename());
+
+        if (shadowFile.exists()) {
+            shadowFile.delete();
+        }
+    }
+
+    /**
+     * install the persisted prepared state as the persisted current state
+     */
+    public void commitShadowState(Object txId)
+    {
+        File stateFile = new File(getStateFilename());
+        File shadowFile = new File(getShadowStateFilename());
+        shadowFile.renameTo(stateFile);
+    }
+
+    /**
+     * load and restore the current persisted service state at the same time re-establishing any shadow
+     * state and lock if appropiate. if no persisted state exists then return null.
+     *
+     * n.b. the subclass must ensure this is only called during initialization of the service
+     */
+
+    protected T restoreState()
+    {
+        File file = new File(getStateFilename());
+        File shadowFile = new File(getShadowStateFilename());
+        // we should only have
+        // 1 no files
+        // 2 a state file
+        // 3 a state file and shadow file
+
+        T current = null;
+        T shadow = null;
+        Object shadowTxId = null;
+
+        FileInputStream fis = null;
+        ObjectInputStream ois = null;
+        if (file.exists()) {
+            //
+            try {
+                fis = new FileInputStream(file);
+                ois = new ObjectInputStream(fis);
+                // ignore committed tx id
+                ois.readObject();
+                current = (T) ois.readObject();
+                ois.close();
+            } catch (IOException e) {
+                System.out.println("error : unable to read current service state " + e);
+            } catch (ClassNotFoundException e) {
+                System.out.println("error : unknown class reading current service state " + e);
+            }
+        }
+
+        try {
+            if (ois != null) {
+                ois.close();
+            } else if (fis != null) {
+                fis.close();
+            }
+        } catch (Exception e) {
+            // ignore
+        }
+
+        fis = null;
+        ois = null;
+
+        if (shadowFile.exists()) {
+            //
+            try {
+                fis = new FileInputStream(shadowFile);
+                ois = new ObjectInputStream(fis);
+                // we need the prepared tx id
+                shadowTxId = ois.readObject();
+                shadow = (T) ois.readObject();
+            } catch (IOException e) {
+                System.out.println("error : unable to read shadow restaurant manager state " + e);
+            } catch (ClassNotFoundException e) {
+                System.out.println("error : unknown class reading shadow restaurant manager state " + e);
+            }
+        }
+
+        try {
+            if (ois != null) {
+                ois.close();
+            } else if (fis != null) {
+                fis.close();
+            }
+        } catch (Exception e) {
+            // ignore
+        }
+
+        if (current != null) {
+            // see if we need to install any shadow satte
+            if (shadow != null) {
+                // reestablish lock which means we cannot proceed with any transactions until recovery kicks
+                // in either to roll us forward or back
+                assert current.isParentOf(shadow);
+                putState(shadowTxId, shadow);
+                preparedTxID = shadowTxId;
+            } else {
+                // no locking required
+                preparedTxID = null;
+            }
+        }
+
+        return current;
+    }
+
+    /**
+     * remove details of all currently known transactions
+     */
+    protected void clearTransactions() {
+        Enumeration<Object> keys = transactions.keys();
+        while (keys.hasMoreElements()) {
+            Object key = keys.nextElement();
+            transactions.remove(key);
+            System.out.println("deleted prepared data for transaction " + key);
+        }
+    }
+
+    /**
+     * bit mask identifying no tx
+     */
+    public static final int TX_TYPE_NONE = 0;
+    /**
+     * bit mask identifying a WS-AT tx
+     */
+    public static final int TX_TYPE_AT = 1;
+    /**
+     * bit mask identifying a WS-BA tx
+     */
+    public static final int TX_TYPE_BA = 2;
+
+    /**
+     * bit mask identifying the union of both TX types
+     */
+    public static final int TX_TYPE_BOTH = TX_TYPE_AT | TX_TYPE_BA;
+
+    /**
+     * method for use by the AT and BA recovery modules to notify that the initial recovery scan
+     * has completed. once both scans have been performed the manager can safely delete any
+     * remaining prepared state for which there is no corresponding participant recovery record
+     * @param scanType
+     */
+    public synchronized void recoveryScanCompleted(int scanType)
+    {
+        completedScans |= scanType;
+    }
+
+    /**
+     * identify the name of file used to store the current service state
+      * @return the name of the file used to store the current service state
+     */
+    public abstract String getStateFilename();
+
+    /**
+     * identify the name of file used to store the shadow service state
+      * @return the name of the file used to store the shadow service state
+     */
+    public abstract String getShadowStateFilename();
+
+    /**
+     * flag used to indicate that a prepare is in progress. updates to restaurantState may not proceed
+     * until this is false and even then only if the updated value for the restaurant state is derived
+     * from the current state. changes to this field must be guarded by synchronizing on the manager instance.
+     */
+
+    private Object preparedTxID;
+
+    /**
+     * The transactions we know about and their associated derived states.
+     */
+    private Hashtable<Object, T> transactions;
+
+    /**
+     * mask identifying whether we have completed an AT recovery scan or a BA recovery scan
+     * when both scans have completed it is safe to roll back any remaining prepared state
+     * changes since there can be no associated participant.
+     */
+    protected int completedScans = 0;
+}

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	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiManager.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -29,38 +29,39 @@
 
 package com.jboss.jbosstm.xts.demo.services.taxi;
 
-import com.arjuna.wst.FaultedException;
-
 import java.util.Hashtable;
-import java.util.Enumeration;
 import java.io.*;
 
 /**
  * The transactional application logic for the Taxi Service
  * <p/>
- * Stores and manages taxi reservations. Knows nothing about Web Services.
- * Understands transactional booking lifecycle: unprepared, prepared, finished.
- * </p>
- * Extended to include support for BA compensation based rollback
- * </p>
- * The manager now maintains an extra list compensatableList
- * </p>
- * changes to preparedList and compensatableList are
- * always shadowed in persistent storage before returning control to clients.
- * </p>
+ * Manages taxi reservations. Knows nothing about Web Services.
+ * <p/>
+ * Taxis are an unlimited resource so this manager does not maintain any
+ * persistent state.
  *
  * @author Jonathan Halliday (jonathan.halliday at arjuna.com)
  * @version $Revision: 1.3 $
  */
 public class TaxiManager implements Serializable
 {
+    /*****************************************************************************/
+    /* Support for the Web Service API                                           */
+    /*****************************************************************************/
+
     /**
-     * Create and initialise a new TaxiManager instance.
+     * Accessor to obtain the singleton taxi manager instance.
+     *
+     * @return the singleton TaxiManager instance.
      */
-    private TaxiManager()
+    public synchronized static TaxiManager getSingletonInstance()
     {
-        setToDefault(false);
-        restoreState();
+        if (singletonInstance == null)
+        {
+            singletonInstance = new TaxiManager();
+        }
+
+        return singletonInstance;
     }
 
     /**
@@ -71,7 +72,7 @@
     public synchronized void bookTaxi(Object txID)
     {
         // locate any pre-existing request for the same transaction
-        Integer request = (Integer) unpreparedTransactions.get(txID);
+        Integer request = (Integer) transactions.get(txID);
         if (request == null)
         {
             // this is the first request for this
@@ -80,301 +81,103 @@
         }
 
         // record the request, keyed to its transaction scope
-        unpreparedTransactions.put(txID, new Integer(request.intValue()));
-        // we don't actually need to update until prepare
+        transactions.put(txID, new Integer(request.intValue()));
     }
 
     /**
-     * Attempt to ensure availability of the requested taxi.
-     *
-     * @param txID The transaction identifier
-     * @return true on success, false otherwise
+     * check whether we have already seen a web service request in a given transaction
      */
-    public synchronized boolean prepareTaxi(Object txID)
-    {
-        // ensure that we have seen this transaction before
-        Integer request = (Integer) unpreparedTransactions.get(txID);
-        if (request == null)
-        {
-            return false; // error: transaction not registered
-        }
-        else
-        {
-            if (autoCommitMode)
-            {
-                // record the prepared transaction
-                preparedTransactions.put(txID, request);
-                unpreparedTransactions.remove(txID);
-                updateState();
-                return true;
-            }
-            else
-            {
-                try
-                {
-                    // wait for a user commit/rollback decision
-                    isPreparationWaiting = true;
-                    synchronized (preparation)
-                    {
-                        preparation.wait();
-                    }
-                    isPreparationWaiting = false;
-                    if (isCommit)
-                    {
-                        // record the prepared transaction
-                        preparedTransactions.put(txID, request);
-                        unpreparedTransactions.remove(txID);
-                        updateState();
-                        return true;
-                    }
-                    else
-                    {
-                        return false;
 
-                    }
-                }
-                catch (Exception e)
-                {
-                    System.err.println("TaxiManager.prepareTaxi(): Unable to stop preparation.");
-                    return false;
-                }
-            }
-        }
+    public synchronized boolean knowsAbout(Object txID)
+    {
+        return transactions.get(txID) != null;
     }
 
+    /*****************************************************************************/
+    /* Support for the AT and BA Participant API implementation                  */
+    /*****************************************************************************/
+
     /**
-     * Release a booked or prepared taxi.
+     * Prepare local state changes for the supplied transaction
      *
      * @param txID The transaction identifier
      * @return true on success, false otherwise
      */
-    public synchronized boolean cancelTaxi(Object txID)
+    public synchronized boolean prepareTaxi(Object txID)
     {
-        boolean success = false;
-
-        // the transaction may be prepared, unprepared or unknown
-
-        if (preparedTransactions.containsKey(txID))
+        // ensure that we have seen this transaction before
+        Integer request = (Integer) transactions.get(txID);
+        if (request == null)
         {
-            // undo the prepare operations
-            preparedTransactions.remove(txID);
-            updateState();
-            success = true;
+            return false;
         }
-        else if (unpreparedTransactions.containsKey(txID))
-        {
-            // undo the booking operations
-            unpreparedTransactions.remove(txID);
-            // we don't need to update state
-            success = true;
-        }
         else
         {
-            success = false; // error: transaction not registered
-        }
+            // see if we need user confirmation
 
-        return success;
-    }
-
-    /**
-     * Compensate a committed booking.
-     *
-     * @param txID The transaction identifier
-     * @return true on success, false otherwise
-     */
-    public synchronized boolean compensateTaxi(Object txID)
-            throws FaultedException
-    {
-        boolean success = false;
-
-        // the transaction must be compensatable
-
-        if (compensatableTransactions.containsKey(txID))
-        {
-            // see if the user wants to report a compensation fault
-
-            if (!autoCommitMode)
-            {
-                try
-                {
-                    // wait for a user commit/rollback decision
-                    isPreparationWaiting = true;
-                    synchronized (preparation)
-                    {
+            if (!autoCommitMode) {
+                // need to wait for the user to decide whether to go ahead or not with this participant
+                isPreparationWaiting = true;
+                synchronized (preparation) {
+                    try {
                         preparation.wait();
+                    } catch (InterruptedException e) {
+                        // ignore
                     }
-                    isPreparationWaiting = false;
+                }
+                isPreparationWaiting = false;
 
-                    // process the user decision
-                    if (!isCommit)
-                    {
-                        throw new FaultedException("TheatreManager.compensateSeats(): compensation fault");
-                    }
+                // process the user decision
+                if (!isCommit) {
+                    return false;
                 }
-                catch (Exception e)
-                {
-                    System.err.println("TaxiManager.compensateTaxi(): Unexpected error during compensation.");
-                    throw new FaultedException("TaxiManager.compensateTaxi(): compensation fault");
-                }
             }
-
-            // compensate the committed operation
-            compensatableTransactions.remove(txID);
-            updateState();
-            success = true;
+            return true;
         }
-        else
-        {
-            success = false; // error: transaction not registered
-        }
-
-        return success;
     }
 
     /**
-     * Commit taxi booking.
+     * commit local state changes for the supplied transaction
      *
-     * @param txID The transaction identifier
-     * @return true on success, false otherwise
+     * @param txID
      */
-    public synchronized boolean commitTaxi(Object txID)
+    public synchronized void commitTaxi(Object txID)
     {
-        return commitTaxi(txID, false);
+        // just need to remove the transaction from the hash map
+        transactions.remove(txID);
     }
 
     /**
-     * Commit taxi booking, possibly allowing subsequent compensation.
+     * roll back local state changes for the supplied transaction
      *
-     * @param txID The transaction identifier
-     * @param compensatable true if it may be necessary to compensate this commit laer
-     * @return true on success, false otherwise
+     * @param txID
      */
-    public synchronized boolean commitTaxi(Object txID, boolean compensatable)
+    public synchronized void rollbackTaxi(Object txID)
     {
-        boolean success = false;
-        hasCommitted = true;
-
-        // the transaction may be prepared, unprepared or unknown
-
-        if (preparedTransactions.containsKey(txID))
-        {
-            // complete the prepared transaction
-            Integer request =  (Integer)preparedTransactions.remove(txID);
-            if (compensatable) {
-                compensatableTransactions.put(txID, request);
-            }
-            updateState();
-            success = true;
-        }
-        else if (unpreparedTransactions.containsKey(txID))
-        {
-            Integer request = (Integer)unpreparedTransactions.remove(txID);
-            boolean doCommit;
-            // check we are ok to go ahead and if so
-            // use one phase commit optimisation, skipping prepare
-
-            if (autoCommitMode)
-            {
-                doCommit = true;
-            }
-            else
-            {
-                try
-                {
-                    // wait for a user decision
-                    isPreparationWaiting = true;
-                    synchronized (preparation)
-                    {
-                        preparation.wait();
-                    }
-                    isPreparationWaiting = false;
-
-                    // process the user decision
-                    doCommit = isCommit;
-                } catch (Exception e) {
-                    System.err.println("RestaurantManager.commitSeats(): Unable to perform commit.");
-                    doCommit = false;
-                }
-            }
-
-            if (doCommit) {
-                if (compensatable) {
-                    compensatableTransactions.put(txID, request);
-                    // we have to update state in this case
-                    updateState();
-                    success = true;
-                } else {
-                    // we don't have to update anything
-                    success = true;
-                }
-            } else {
-                // we don't have to update anything
-                success = false;
-            }
-        }
-        else
-        {
-            success = false; // error: transaction not registered
-        }
-
-        return success;
+        // just need to remove the transaction from the hash map
+        transactions.remove(txID);
     }
 
     /**
-     * Close taxi bookings, removing possibility for compensation.
-     *
-     * @param txID The transaction identifier
-     * @return true on success, false otherwise
+     * handle a recovery error by rolling back the changes associated with the transaction
+     * @param txID
      */
-    public synchronized boolean closeTaxi(Object txID)
+    public void error(String txID)
     {
-        boolean success;
-
-        // the transaction may be compensatable or unknown
-
-        if (compensatableTransactions.containsKey(txID))
-        {
-            // complete the prepared transaction
-            compensatableTransactions.remove(txID);
-            updateState();
-            success = true;
-        }
-        else
-        {
-            success = false; // error: transaction not registered for compensation
-        }
-
-        return success;
+        rollbackTaxi(txID);
     }
 
-    /**
-     * Handle BA error for a specific booking.
-     *
-     * @param txID The transaction identifier
-     */
-    public synchronized void error(Object txID)
-    {
-        // undo any provisional or actual changes associated wiht the booking
+    /*****************************************************************************/
+    /* Accessors for the GUI to view and reset the service state                 */
+    /*****************************************************************************/
 
-        Integer request = (Integer) unpreparedTransactions.remove(txID);
-        if (request != null) {
-        } else if ((request = (Integer)preparedTransactions.remove(txID)) != null) {
-            updateState();
-        } else if ((request = (Integer)compensatableTransactions.remove(txID)) != null) {
-            updateState();
-        }
-    }
-
     /**
-     * Determine if a specific transaction is known to the business logic.
-     *
-     * @param txID The uniq id for the transaction
-     * @return true if the business logic is holding state related to the given txID,
-     *         false otherwise.
+     * Reset to the initial state.
      */
-    public boolean knowsAbout(Object txID)
+    public synchronized void reset()
     {
-        return (unpreparedTransactions.containsKey(txID) || preparedTransactions.containsKey(txID));
+        // clear the hash map
+        transactions.clear();
     }
 
     /**
@@ -435,64 +238,35 @@
         isCommit = commit;
     }
 
-    /**
-     * (re-)initialise the instance data structures deleting any previously saved
-     * transaction state.
-     */
-    public void setToDefault()
-    {
-        setToDefault(true);
-    }
+    /*****************************************************************************/
+    /* Recovery methods maintaining consistency of local and  WSAT/WSBA state    */
+    /*****************************************************************************/
 
     /**
-     * (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
+     * called by the AT recovery module when an AT participant is recovered from a log record
      */
-    public void setToDefault(boolean deleteSavedState)
+    public void recovered(TaxiParticipantAT participant)
     {
-        compensatableTransactions = new Hashtable();
-        preparedTransactions = new Hashtable();
-        unpreparedTransactions = new Hashtable();
-        autoCommitMode = true;
-        preparation = new Object();
-        isPreparationWaiting = false;
-        isCommit = false;
-        hasCommitted = false;
-        if (deleteSavedState) {
-            // just write the current state.
-            updateState();
-        }
+        // nothing needed here
     }
 
     /**
-     * Allow use of a singleton model for web services demo.
+     * called by the BA recovery module when an AT participant is recovered from a log record
      */
-    public synchronized static TaxiManager getSingletonInstance()
+    public void recovered(TaxiParticipantBA participant)
     {
-        if (singletonInstance == null)
-        {
-            singletonInstance = new TaxiManager();
-        }
-
-        return singletonInstance;
+        // nothing needed here
     }
 
-    public boolean hasBeenCommitted()
+    public void recoveryScanCompleted(int txType)
     {
-        return hasCommitted;
+        // nothing needed here
     }
 
-    public Hashtable getPreparedTransactions()
-    {
-        return preparedTransactions;
-    }
+    /*****************************************************************************/
+    /* Private implementation                                                    */
+    /*****************************************************************************/
 
-    public Hashtable getUnpreparedTransactions()
-    {
-        return unpreparedTransactions;
-    }
-
     /**
      * A singleton instance of this class.
      */
@@ -501,19 +275,9 @@
     /**
      * The transactions we know about but which have not been prepared.
      */
-    private Hashtable unpreparedTransactions;
+    private Hashtable transactions;
 
     /**
-     * The transactions we know about and are prepared to commit.
-     */
-    private Hashtable preparedTransactions;
-
-    /**
-     * The transactions we know about and are prepared to commit.
-     */
-    private Hashtable compensatableTransactions;
-
-    /**
      * The auto commit mode.
      * <p/>
      * true = automatically commit, false = manually commit
@@ -526,11 +290,6 @@
     private boolean isCommit;
 
     /**
-     * If the participant has already been commmitted or not.
-     */
-    private boolean hasCommitted = false;
-
-    /**
      * The object used for wait/notify in manual commit mode.
      */
     private Object preparation;
@@ -541,125 +300,15 @@
     private boolean isPreparationWaiting;
 
     /**
-     * the name of the file used to store the restaurant manager state
+     * Create and initialise a new TaxiManager instance.
      */
-    final static private String STATE_FILENAME = "taxiManagerState";
-
-    /**
-     * the name of the file used 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()
+    private TaxiManager()
     {
-        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");
-        }
-    }
+        transactions = new Hashtable();
+        preparation = new Object();
 
-    /**
-     * 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);
+        isCommit = true;
+        autoCommitMode = true;
+        isPreparationWaiting = false;
     }
-
-    /**
-     * 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
-    {
-        compensatableTransactions = new Hashtable();
-        String name = (String)ois.readObject();
-        while (!"".equals(name)) {
-            int count = ois.readInt();
-            compensatableTransactions.put(name, new Integer(count));
-            name = (String)ois.readObject();
-        }
-        preparedTransactions = new Hashtable();
-        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 = compensatableTransactions.keys();
-        while (keys.hasMoreElements()) {
-            String name = (String)keys.nextElement();
-            int count = ((Integer)compensatableTransactions.get(name)).intValue();
-            oos.writeObject(name);
-            oos.writeInt(count);
-        }
-        oos.writeObject("");
-        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	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiParticipantAT.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -43,6 +43,9 @@
  */
 public class TaxiParticipantAT implements Durable2PCParticipant, Serializable
 {
+    /************************************************************************/
+    /* public methods                                                       */
+    /************************************************************************/
     /**
      * Participant instances are related to transaction instances
      * in a one to one manner.
@@ -56,6 +59,9 @@
         this.txID = txID;
     }
 
+    /************************************************************************/
+    /* Durable2PCParticipant methods                                        */
+    /************************************************************************/
     /**
      * Invokes the prepare step of the business logic,
      * reporting activity and outcome.
@@ -86,7 +92,6 @@
         }
         else
         {
-            getTaxiManager().cancelTaxi(txID) ;
             getTaxiView().addMessage("Prepare failed (not enough Taxis?) Returning 'Aborted'\n");
             getTaxiView().updateFields();
             return new Aborted();
@@ -109,19 +114,10 @@
 
         getTaxiView().addMessage("id:" + txID + ". Commit called on participant: " + this.getClass().toString());
 
-        boolean success = getTaxiManager().commitTaxi(txID);
+        getTaxiManager().commitTaxi(txID);
 
-        // Log the outcome
+        getTaxiView().addMessage("Taxi committed\n");
 
-        if (success)
-        {
-            getTaxiView().addMessage("Taxi committed\n");
-        }
-        else
-        {
-            getTaxiView().addMessage("Something went wrong (Transaction not registered?)\n");
-        }
-
         getTaxiView().updateFields();
     }
 
@@ -141,35 +137,13 @@
 
         getTaxiView().addMessage("id:" + txID + ". Rollback called on participant: " + this.getClass().toString());
 
-        boolean success = getTaxiManager().cancelTaxi(txID);
+        getTaxiManager().rollbackTaxi(txID);
 
-        // Log the outcome
+        getTaxiView().addMessage("Taxi booking cancelled\n");
 
-        if (success)
-        {
-            getTaxiView().addMessage("Taxi booking cancelled\n");
-        }
-        else
-        {
-            getTaxiView().addMessage("Something went wrong (Transaction not registered?)\n");
-        }
-
         getTaxiView().updateFields();
     }
 
-    /**
-     * Shortcut method which combines the prepare
-     * and commit steps in a single operation.
-     *
-     * @throws WrongStateException
-     * @throws SystemException
-     */
-    public void commitOnePhase() throws WrongStateException, SystemException
-    {
-        prepare();
-        commit();
-    }
-
     public void unknown() throws SystemException
     {
         // used for calbacks during crash recovery. This impl is not recoverable

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiParticipantBA.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiParticipantBA.java	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiParticipantBA.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -41,8 +41,13 @@
  * @author Jonathan Halliday (jonathan.halliday at arjuna.com)
  * @version $Revision: 1.2 $
  */
-public class TaxiParticipantBA implements BusinessAgreementWithParticipantCompletionParticipant, Serializable
+public class TaxiParticipantBA
+        implements BusinessAgreementWithParticipantCompletionParticipant,
+        Serializable
 {
+    /************************************************************************/
+    /* public methods                                                       */
+    /************************************************************************/
     /**
      * Participant instances are related to business method calls
      * in a one to one manner.
@@ -55,6 +60,9 @@
         this.txID = txID;
     }
 
+    /************************************************************************/
+    /* BusinessAgreementWithParticipantCompletionParticipant methods        */
+    /************************************************************************/
     /**
      * The transaction has completed successfully. The participant previously
      * informed the coordinator that it was ready to complete.
@@ -65,18 +73,13 @@
 
     public void close() throws WrongStateException, SystemException
     {
-        // let the manager know that this activity no longer requires the option of compensation
+        // nothing to do here as the taxi is already booked
 
+
         System.out.println("TaxiParticipantBA.close");
 
-        if (!getTaxiManager().closeTaxi(txID)) {
-            // throw a WrongStateException to indicate that we were not expecting a close
-            System.out.println("TaxiParticipantBA.close : not expecting a close for BA participant " + txID);
-
-            throw new WrongStateException("Unexpected close for BA participant " + txID);
-        }
-
         getTaxiView().addMessage("id:" + txID + ". Close called on participant: " + this.getClass());
+
         getTaxiView().updateFields();
     }
 
@@ -91,18 +94,14 @@
 
     public void cancel() throws WrongStateException, SystemException
     {
-        // let the manager know that this activity is being cancelled
+        // let the manager know that this activity has been cancelled
 
         System.out.println("TaxiParticipantBA.cancel");
 
-        if (!getTaxiManager().cancelTaxi(txID)) {
-            // throw a WrongStateException to indicate that we were not expecting a close
-            System.out.println("TaxiParticipantBA.cancel : not expecting a cancel for BA participant " + txID);
+        getTaxiManager().rollbackTaxi(txID);
 
-            throw new WrongStateException("Unexpected cancel for BA participant " + txID);
-        }
-
         getTaxiView().addMessage("id:" + txID + ". Cancel called on participant: " + this.getClass().toString());
+
         getTaxiView().updateFields();
     }
 
@@ -123,25 +122,8 @@
 
         getTaxiView().updateFields();
 
-        // tell the manager to compensate
-
-        try {
-            if (!getTaxiManager().compensateTaxi(txID)) {
-                // throw a WrongStateException to indicate that we were not expecting a close
-                System.out.println("RestaurantParticipantBA.compensate : not expecting a compensate for BA participant " + txID);
-
-                getTaxiView().addMessage("id:" + txID + ". Failed to compensate participant: " + this.getClass().toString());
-                getTaxiView().updateFields();
-
-                throw new WrongStateException("Unexpected compensate for BA participant " + txID);
-            }
-        } catch (FaultedException fe) {
-            getTaxiView().addMessage("id:" + txID + ". FaultedException when compensating participant: " + this.getClass().toString());
-
-            getTaxiView().updateFields();
-            throw fe;
-        }
-
+        // there is no need to compensate a completed taxi booking as the taxi will just pick up another punter
+        
         getTaxiView().addMessage("id:" + txID + ". Compensated participant: " + this.getClass().toString());
         getTaxiView().updateFields();
     }
@@ -173,6 +155,9 @@
         getTaxiView().updateFields();
     }
 
+    /************************************************************************/
+    /* private implementation                                               */
+    /************************************************************************/
     /**
      * Id for the transaction which this participant instance relates to.
      * Set by the service (via contrtuctor) at enrolment time, this value

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	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiServiceAT.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -33,14 +33,11 @@
 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
@@ -57,26 +54,6 @@
 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.
@@ -113,6 +90,7 @@
 
         taxiView.addMessage("id:" + transactionId.toString() + ". Received a taxi booking request");
 
+        // invoke the backend business logic:
         TaxiManager.getSingletonInstance().bookTaxi(transactionId);
 
         taxiView.addMessage("Request complete\n");

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiServiceBA.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiServiceBA.java	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/taxi/TaxiServiceBA.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -93,53 +93,78 @@
         taxiView.addPrepareMessage("id:" + transactionId.toString() + ". Received a taxi booking request");
         taxiView.updateFields();
 
+        if (taxiManager.knowsAbout(transactionId)) {
+            // hmm, this means we have already completed changes in this transaction and are awaiting a close
+            //or compensate request. this service does not support repeated requests in the same activity so
+            // we fail this request.
+
+            taxiView.addMessage("id:" + transactionId + ". Participant already enrolled!");
+            taxiView.updateFields();
+            System.err.println("bookSeats: request failed");
+            return false;
+        }
+
+        TaxiParticipantBA taxiParticipant = new TaxiParticipantBA(transactionId);
+        BAParticipantManager participantManager;
+
+        // enlist the Participant for this service:
+        try
+        {
+            participantManager = activityManager.enlistForBusinessAgreementWithParticipantCompletion(taxiParticipant, "org.jboss.jbossts.xts-demo:taxiBA:" + new Uid().toString());
+        }
+        catch (Exception e)
+        {
+            taxiView.addMessage("id:" + transactionId + ". Participant enrolement failed");
+            taxiManager.rollbackTaxi(transactionId);
+            System.err.println("bookTaxi: Participant enrolment failed");
+            e.printStackTrace(System.err);
+            return false;
+        }
+
         // invoke the backend business logic:
         taxiManager.bookTaxi(transactionId);
 
-        // attempt to finalise the booking
+        // this service employs the participant completion protocol which means it decides when it wants to
+        // commit local changes. so we prepare and commit those changes now. if any other participant fails
+        // or the client decides to cancel we can rely upon being told to compensate.
+
         if (taxiManager.prepareTaxi(transactionId))
         {
             taxiView.addMessage("id:" + transactionId + ". Seats prepared, trying to commit and enlist compensation Participant");
             taxiView.updateFields();
 
             // it worked, so now we need a participant enlisted in case of compensation:
-            TaxiParticipantBA taxiParticipant = new TaxiParticipantBA(transactionId);
-            // enlist the Participant for this service:
-            BAParticipantManager participantManager = null;
+
             try
             {
-                participantManager = activityManager.enlistForBusinessAgreementWithParticipantCompletion(taxiParticipant, "org.jboss.jbossts.xts-demo:restaurantBA:" + new Uid().toString());
+                // tell the participant manager we have finished our work
+                // this will call back to the participant once the recovery record has been written
+                // allowing it to commit or roll back the restaurant manager
+                participantManager.completed();
             }
             catch (Exception e)
             {
-                taxiView.addMessage("id:" + transactionId + ". Participant enrolement failed");
-                taxiManager.cancelTaxi(transactionId);
-                System.err.println("bookTaxi: Participant enrolment failed");
+                System.err.println("bookTaxi: 'completed' callback failed");
+                taxiManager.rollbackTaxi(transactionId);
                 e.printStackTrace(System.err);
                 return false;
             }
-
-            // finish the booking in the backend ensuring it is compensatable:
-            taxiManager.commitTaxi(transactionId, true);
-
+        }
+        else
+        {
+            taxiView.addMessage("id:" + transactionId + ". Failed to reserve taxi. Cancelling.");
+            taxiView.updateFields();
             try
             {
-                // tell the manager we have finished our work:
-                participantManager.completed();
+                // tell the participant manager we cannot complete. this will force the activity to fail
+                participantManager.cannotComplete();
             }
             catch (Exception e)
             {
-                System.err.println("bookTaxi: 'completed' callback failed");
-                taxiManager.cancelTaxi(transactionId);
+                System.err.println("bookSeats: 'cannotComplete' callback failed");
                 e.printStackTrace(System.err);
                 return false;
             }
-        }
-        else
-        {
-            taxiView.addMessage("id:" + transactionId + ". Failed to reserve taxi. Cancelling.");
-            taxiManager.cancelTaxi(transactionId);
-            taxiView.updateFields();
             return false;
         }
 

Added: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreConstants.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreConstants.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreConstants.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -0,0 +1,42 @@
+package com.jboss.jbosstm.xts.demo.services.theatre;
+
+/**
+ * Constant values used by the theatre service
+ */
+public class TheatreConstants
+{
+    /**
+     * Constant (array index) used for the seating area CIRCLE.
+     */
+    public static final int CIRCLE = 0;
+
+    /**
+     * Constant (array index) used for the seating area STALLS.
+     */
+    public static final int STALLS = 1;
+
+    /**
+     * Constant (array index) used for the seating area BALCONY.
+     */
+    public static final int BALCONY = 2;
+
+    /**
+     * The total number (array size) of seating areas.
+     */
+    public static final int NUM_SEAT_AREAS = 3;
+
+    /**
+     * 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 public String STATE_FILENAME = "theatreManagerState";
+
+    /**
+     * the name of the file used to store the restaurant manager shadow state
+     */
+    final static public String SHADOW_STATE_FILENAME = "theatreManagerShadowState";
+}

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	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreManager.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -29,47 +29,57 @@
 
 package com.jboss.jbosstm.xts.demo.services.theatre;
 
-import com.arjuna.wst.FaultedException;
+import com.jboss.jbosstm.xts.demo.services.state.ServiceStateManager;
 
-import java.util.Hashtable;
-import java.util.Enumeration;
+import javax.xml.ws.WebServiceException;
+
+import static com.jboss.jbosstm.xts.demo.services.theatre.TheatreConstants.*;
+
 import java.io.*;
 
 /**
  * The transactional application logic for the Theatre Service.
  * <p/>
- * Stores and manages seating reservations. Knows nothing about Web Services.
- * Understands transactional booking lifecycle: unprepared, prepared, finished.
+ * Stores and manages seating reservations.
+ * <p/>
+ * The manager extends class ServiceStateManager which implements a very simple
+ * transactional resource manager. It gives the theatre manager the ability to
+ * persist the web service state in a local disk file and to make transactional
+ * updates to that persistent state. The unit of locking is the whole of the
+ * service state so although bookings can be attempted by concurrent transactions
+ * only one such booking will commit, forcing other concurrent transactions to
+ * roll back. Conflict detection is implemented using a simple versioning scheme.
  *
- * </p>The manager maintains the following invariants regarding seating capacity:
- * <ul>
- * <li>nBooked[area] == sum(unpreparedList.seatCount[area]) + sum(preparedList.seatCount[area])
+ * The theatre manager provides a book method allowing the web service endpoint to book
+ * or unbook seats. It also exposes prepare, commit and rollback operations used by both
+ * WSAT and WSBA participants to drive prepare, commit and rollback of changes to the
+ * persistent state. Finally it exposes recovery logic used by the WSAT and WSBA recovery
+ * modules to tie recovery of WSAT and WSBA participants to recovery and rollback of the
+ * local service state.
  *
- * <li>nPrepared[area] = sum(prepared.seatCount[area])
- *
- * <li>nTotal[area] == nFree[area] + nPrepared[area] + nCommitted[area]
- * </ul>
- * Extended to include support for BA compensation based rollback
- * </p>
- * The manager now maintains an extra list compensatableList:
- *  <ul>
- * <li>nCompensatable[area] == sum(compensatableList.seatCount[area])
- * </ul>
- * changes to nPrepared, nFree, nCommitted, nCompensatable, nTotal, preparedList and compensatableList are
- * always shadowed in persistent storage before returning control to clients.
- * </p>
  * @author Jonathan Halliday (jonathan.halliday at arjuna.com)
+ * @author Andrew Dinn (adinn at redhat.com)
  * @version $Revision: 1.4 $
  */
-public class TheatreManager implements Serializable
+public class TheatreManager extends ServiceStateManager<TheatreState>
 {
+    /*****************************************************************************/
+    /* Support for the Web Service API                                           */
+    /*****************************************************************************/
+
     /**
-     * Create and initialise a new TheatreManager instance.
+     * Accessor to obtain the singleton theatre manager instance.
+     *
+     * @return the singleton TheatreManager instance.
      */
-    public TheatreManager()
+    public synchronized static TheatreManager getSingletonInstance()
     {
-        setToDefault(false);
-        restoreState();
+        if (singletonInstance == null)
+        {
+            singletonInstance = new TheatreManager();
+        }
+
+        return singletonInstance;
     }
 
     /**
@@ -81,425 +91,217 @@
      */
     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);
-        if (requests == null)
-        {
-            // this is the first request for this transaction
-            // setup the data structure to record it
-            requests = new Integer[NUM_SEAT_AREAS];
-            for (int i = 0; i < NUM_SEAT_AREAS; i++)
-            {
-                requests[i] = new Integer(0);
+        // we cannot proceed while a prepare is in progress
+
+        while (isLocked()) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                // ignore
             }
         }
 
-        // record the request, keyed to its transaction scope
-        requests[area] = new Integer(requests[area].intValue() + nSeats);
-        unpreparedTransactions.put(txID, requests);
+        if (theatreState.freeSeats[area] < nSeats ||
+                theatreState.bookedSeats[area] + nSeats > theatreState.totalSeats[area]) {
+            throw new WebServiceException("requested number of seats (" + nSeats + ") not available");
+        }
 
-        // record the increased commitment to provide seating
-        nBookedSeats[area] += nSeats;
-        // we don't actually need to update until prepare
-    }
+        // create a state derived from the current state which reflects the new booking count
 
-    /**
-     * Attempt to ensure availability of the requested seating.
-     *
-     * @param txID The transaction identifier
-     * @return true on success, false otherwise
-     */
-    public synchronized boolean prepareSeats(Object txID)
-    {
-        int[] nSeats = new int[NUM_SEAT_AREAS];
+        TheatreState childState =  theatreState.derivedState();
 
-        // ensure that we have seen this transaction before
-        Integer[] requests = (Integer[]) unpreparedTransactions.get(txID);
-        if (requests == null)
-        {
-            return false; // error: transaction not registered
-        }
-        else
-        {
-            // determine the number of seats available
-            for (int i = 0; i < NUM_SEAT_AREAS; i++)
-            {
-                nSeats[i] = nFreeSeats[i];
-                nSeats[i] -= requests[i].intValue();
-            }
-            if (autoCommitMode)
-            {
-                boolean success = true;
-                // check we have enough seats avaiable
-                for (int i = 0; i < NUM_SEAT_AREAS; i++)
-                {
-                    if (nSeats[i] < 0)
-                    {
-                        success = false; // error: not enough seats
-                    }
-                }
-                if (success)
-                {
-                    // record the prepared transaction
-                    preparedTransactions.put(txID, requests);
-                    unpreparedTransactions.remove(txID);
-                    // mark the prepared seats as unavailable
-                    for (int i = 0; i < NUM_SEAT_AREAS; i++)
-                    {
-                        nFreeSeats[i] = nSeats[i];
-                        nPreparedSeats[i] += requests[i].intValue();
-                    }
-                    updateState();
-                }
-                return success;
-            }
-            else
-            {
-                try
-                {
-                    // wait for a user commit/rollback decision
-                    isPreparationWaiting = true;
-                    synchronized (preparation)
-                    {
-                        preparation.wait();
-                    }
-                    isPreparationWaiting = false;
+        // update the number of booked and free seats in the derived state
 
-                    // process the user decision
-                    if (isCommit)
-                    {
-                        // record the prepared transaction
-                        preparedTransactions.put(txID, requests);
-                        unpreparedTransactions.remove(txID);
-                        // mark the prepared seats as unavailable
-                        for (int i = 0; i < NUM_SEAT_AREAS; i++)
-                        {
-                            nFreeSeats[i] = nSeats[i];
-                            nPreparedSeats[i] += requests[i].intValue();
-                        }
-                        updateState();
-                        return true;
-                    }
-                    else
-                    {
-                        return false;
-                    }
-                }
-                catch (Exception e)
-                {
-                    System.err.println("TheatreManager.prepareSeats(): Unable to stop preparation.");
-                    return false;
-                }
-            }
-        }
+        childState.freeSeats[area] -= nSeats;
+        childState.bookedSeats[area] += nSeats;
+
+        // install this as the current transaction state
+        
+        putState(txID, childState);
     }
 
     /**
-     * Release booked or prepared seats.
-     *
-     * @param txID The transaction identifier
-     * @return true on success, false otherwise
+     * check whether we have already seen a web service request in a given transaction
      */
-    public boolean cancelSeats(Object txID)
-    {
-        boolean success = false;
 
-        // the transaction may be prepared, unprepared or unknown
-
-        if (preparedTransactions.containsKey(txID))
-        {
-            // undo the prepare operations
-            Integer[] requests = (Integer[]) preparedTransactions.remove(txID);
-            for (int i = 0; i < NUM_SEAT_AREAS; i++)
-            {
-                nFreeSeats[i] += requests[i].intValue();
-                nPreparedSeats[i] -= requests[i].intValue();
-                nBookedSeats[i] -= requests[i].intValue();
-            }
-            updateState();
-            success = true;
-        }
-        else if (unpreparedTransactions.containsKey(txID))
-        {
-            // undo the booking operations
-            Integer[] requests = (Integer[]) unpreparedTransactions.remove(txID);
-            for (int i = 0; i < NUM_SEAT_AREAS; i++)
-            {
-                nBookedSeats[i] -= requests[i].intValue();
-            }
-            // we don't need to update state
-            success = true;
-        }
-        else
-        {
-            success = false; // error: transaction not registered
-        }
-
-        return success;
+    public boolean knowsAbout(Object txID)
+    {
+        return getState(txID) != null;
     }
 
+    /*****************************************************************************/
+    /* Support for the AT and BA Participant API implementation                  */
+    /*****************************************************************************/
+
     /**
-     * Compensate a booking.
+     * Prepare local state changes for the supplied transaction
      *
      * @param txID The transaction identifier
      * @return true on success, false otherwise
      */
-    public boolean compensateSeats(Object txID)
-            throws FaultedException
+    public boolean prepareSeats(Object txID)
     {
-        boolean success = false;
+        // ensure that we have seen this transaction before
+        TheatreState childState = getState(txID);
+        if (childState == null) {
+            return false;
+        }
+        // we have a single monolithic state element which means that only one transaction can prepare
+        // at any given time. we lock this state at prepare by providing the txId as a locking id. it only
+        // gets unlocked when we reach commit or rollback. the equivalent to the lock in memory is the
+        // shadow state file on disk.
+        synchronized (this) {
+            while (isLocked()) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                    // ignore
+                }
+            }
 
-        // the transaction must be compensatable
+            // check no other bookings have been committed
 
-        if (compensatableTransactions.containsKey(txID))
-        {
-            // see if the user wants to report a compensation fault
+            if (!theatreState.isParentOf(childState)) {
+                removeState(txID);
+                return false;
+            }
 
-            if (!autoCommitMode)
-            {
-                try
-                {
-                    // wait for a user commit/rollback decision
-                    isPreparationWaiting = true;
-                    synchronized (preparation)
-                    {
+            // see if we need user confirmation
+
+            if (!autoCommitMode) {
+                // need to wait for the user to decide whether to go ahead or not with this participant
+                isPreparationWaiting = true;
+                synchronized (preparation) {
+                    try {
                         preparation.wait();
+                    } catch (InterruptedException e) {
+                        // ignore
                     }
-                    isPreparationWaiting = false;
+                }
+                isPreparationWaiting = false;
 
-                    // process the user decision
-                    if (!isCommit)
-                    {
-                        throw new FaultedException("TheatreManager.compensateSeats(): compensation fault");
-                    }
+                // process the user decision
+                if (!isCommit) {
+                    removeState(txID);
+                    return false;
                 }
-                catch (Exception e)
-                {
-                    System.err.println("TheatreManager.compensateSeats(): Unexpected error during compensation.");
-                    throw new FaultedException("TheatreManager.compensateSeats(): compensation fault");
-                }
             }
 
-            // compensate the prepared transaction
-            Integer[] requests = (Integer[]) compensatableTransactions.remove(txID);
-            for (int i = 0; i < NUM_SEAT_AREAS; i++)
-            {
-                nCompensatableSeats[i] -= requests[i].intValue();
-                nCommittedSeats[i] -= requests[i].intValue();
-                nFreeSeats[i] += requests[i].intValue();
+            // ok, so lock the state against other prepare/commits
+
+            lock(txID);
+        }
+        // if we got here then no other changes have invalidated our booking and we have locked out
+        // further changes until commit or rollback occurs. we write the derived child state to the
+        // shadow state file before returning. if we crash after the write we will detect the shadow
+        // state at reboot and restore the lock.
+        try {
+            writeShadowState(txID, childState);
+            return true;
+        } catch (Exception e) {
+            clearShadowState(txID);
+            synchronized (this) {
+                removeState(txID);
+                unlock();
             }
-            updateState();
-            success = true;
+            System.err.println("RestaurantManager.prepareSeats(): Error attempting to prepare transaction: " + e);
+            return false;
         }
-        else
-        {
-            success = false; // error: transaction not registered
-        }
-
-        return success;
     }
 
     /**
-     * Commit seat bookings.
+     * commit local state changes for the supplied transaction
      *
-     * @param txID The transaction identifier
-     * @return true on success, false otherwise
+     * @param txID
      */
-    public synchronized boolean commitSeats(Object txID)
+    public void commitSeats(Object txID)
     {
-        return commitSeats(txID, false);
+        synchronized (this) {
+            // if there is a shadow state with this id then we need to copy the shadow state file over to the
+            // real state file. it may be that there is no shadow state because this is a repeated commit
+            // request. if so then we must have committed earlier so there is no harm done.
+            if (isLockID(txID)) {
+                commitShadowState(txID);
+                // update the current state with the prepared state.
+                theatreState = getPreparedState();
+                unlock();
+            }
+            removeState(txID);
+        }
     }
 
     /**
-     * Commit seat bookings, possibly allowing subsequent compensation.
-     *
-     * @param txID The transaction identifier
-     * @param compensatable true if it may be necessary to compensate this commit laer
-     * @return true on success, false otherwise
+     * roll back local state changes for the supplied transaction
+     * @param txID
      */
-    public synchronized boolean commitSeats(Object txID, boolean compensatable)
+    public void rollbackSeats(Object txID)
     {
-        boolean success = false;
-
-        // the transaction may be prepared, unprepared or unknown
-
-        if (preparedTransactions.containsKey(txID))
-        {
-
-            // complete the prepared transaction
-            Integer[] requests = (Integer[]) preparedTransactions.remove(txID);
-            if (compensatable)
-            {
-                compensatableTransactions.put(txID, requests);
+        synchronized (this) {
+            removeState(txID);
+            if (isLockID(txID)) {
+                clearShadowState(txID);
+                unlock();
             }
-            for (int i = 0; i < NUM_SEAT_AREAS; i++)
-            {
-                if (compensatable) {
-                    nCompensatableSeats[i] += requests[i].intValue();
-                }
-                nCommittedSeats[i] += requests[i].intValue();
-                nPreparedSeats[i] -= requests[i].intValue();
-                nBookedSeats[i] -= requests[i].intValue();
-            }
-            updateState();
-            success = true;
+            this.notifyAll();
         }
-        else if (unpreparedTransactions.containsKey(txID))
-        {
-            // use one phase commit optimisation, skipping prepare
-            Integer[] requests = (Integer[]) unpreparedTransactions.remove(txID);
-            boolean doCommit = true;
-            // check we have enough seats and if so
-            // use one phase commit optimisation, skipping prepare
-
-            if (autoCommitMode)
-            {
-                for (int i = 0; doCommit && i < NUM_SEAT_AREAS; i++)
-                {
-                    if (requests[i].intValue() > nFreeSeats[i])
-                    {
-                        doCommit = false;
-                    }
-                }
-            }
-            else
-            {
-                try
-                {
-                    // wait for a user decision
-                    isPreparationWaiting = true;
-                    synchronized (preparation)
-                    {
-                        preparation.wait();
-                    }
-                    isPreparationWaiting = false;
-
-                    // process the user decision
-                    doCommit = isCommit;
-                } catch (Exception e) {
-                    System.err.println("TheatreManager.commitSeats(): Unable to perform commit.");
-                    doCommit = false;
-                }
-            }
-
-            if (doCommit) {
-                if (compensatable) {
-                    compensatableTransactions.put(txID, requests);
-                }
-                for (int i = 0; i < NUM_SEAT_AREAS; i++)
-                {
-                    if (compensatable) {
-                        nCompensatableSeats[i] += requests[i].intValue();
-                    }
-                    nCommittedSeats[i] += requests[i].intValue();
-                    nFreeSeats[i] -= requests[i].intValue();
-                    nBookedSeats[i] -= requests[i].intValue();
-                }
-                updateState();
-                success = true;
-            } else {
-                // get rid of the commitment to keep these seats
-                for (int i = 0; i < NUM_SEAT_AREAS; i++)
-                {
-                    nBookedSeats[i] -= requests[i].intValue();
-                }
-                success = false;
-            }
-        }
-        else
-        {
-            success = false; // error: transaction not registered
-        }
-
-        return success;
     }
 
     /**
-     * Close seat bookings, removing possibility for compensation.
+     * Handle BA error for a specific booking.
      *
      * @param txID The transaction identifier
-     * @return true on success, false otherwise
      */
-    public synchronized boolean closeSeats(Object txID)
+    public synchronized void error(Object txID)
     {
-        boolean success;
-
-        // the transaction may be compensatable or unknown
-
-        if (compensatableTransactions.containsKey(txID))
-        {
-            // complete the prepared transaction
-            Integer[] requests = (Integer[]) compensatableTransactions.remove(txID);
-            for (int i = 0; i < NUM_SEAT_AREAS; i++) {
-                nCompensatableSeats[i] -= requests[i].intValue();
-            }
-            updateState();
-            success = true;
-        }
-        else
-        {
-            success = false; // error: transaction not registered for compensation
-        }
-
-        return success;
+        rollbackSeats(txID);
     }
 
+    /*****************************************************************************/
+    /* Accessors for the GUI to view and reset the service state                 */
+    /*****************************************************************************/
+
     /**
-     * Handle BA error for a specific booking.
+     * Reset to the initial state.
      *
-     * @param txID The transaction identifier
+     * @param txID   The transaction identifier
+     * @param nSeats The number of seats requested
      */
-    public synchronized void error(Object txID)
+    public synchronized void reset()
     {
-        // undo any provisional or actual changes associated wiht the booking
+        // we cannot proceed while a prepare is in progress
 
-        Integer[] request = (Integer[]) unpreparedTransactions.remove(txID);
-        if (request != null) {
-            for (int i = 0; i < NUM_SEAT_AREAS; i++) {
-                nBookedSeats[i] -= request[i].intValue();
+        while (isLocked()) {
+            try {
+                wait();
+            } catch (InterruptedException e) {
+                // ignore
             }
-        } else if ((request = (Integer[])preparedTransactions.remove(txID)) != null) {
-            for (int i = 0; i < NUM_SEAT_AREAS; i++) {
-                nFreeSeats[i] += request[i].intValue();
-                nPreparedSeats[i] -= request[i].intValue();
-                nBookedSeats[i] -= request[i].intValue();
-            }
-            updateState();
-        } else if ((request = (Integer[])compensatableTransactions.remove(txID)) != null) {
-            for (int i = 0; i < NUM_SEAT_AREAS; i++) {
-                nCompensatableSeats[i] -= request[i].intValue();
+        }
 
-                nCommittedSeats[i] -= request[i].intValue();
-                nFreeSeats[i] += request[i].intValue();
-            }
-            updateState();
+        // undo all existing bookings
+
+        TheatreState resetState = theatreState.derivedState();
+
+        for (int area = 0; area < NUM_SEAT_AREAS; area++) {
+            resetState.totalSeats[area] = DEFAULT_SEATING_CAPACITY;
+            resetState.bookedSeats[area] = 0;
+            resetState.freeSeats[area] = DEFAULT_SEATING_CAPACITY;
         }
-    }
 
-    /**
-     * Determine if a specific transaction is known to the business logic.
-     *
-     * @param txID The uniq id for the transaction
-     * @return true if the business logic is holding state related to the given txID,
-     *         false otherwise.
-     */
-    public boolean knowsAbout(Object txID)
-    {
-        return (unpreparedTransactions.containsKey(txID) || preparedTransactions.containsKey(txID));
-    }
+        Object txId = "reset-transaction";
+        try {
+            writeShadowState(txId, resetState);
+            commitShadowState(txId);
+        } catch (IOException e) {
+            clearShadowState(txId);
+             System.out.println("error : unable to reset theatre manager state " + e);
+        }
+        // remove any in-progress transactions
 
-    /**
-     * Change the capacity of a given seating area.
-     *
-     * @param area   The seating area to change
-     * @param nSeats The new capacity for the area
-     */
-    public void newCapacity(int area, int nSeats)
-    {
-        nFreeSeats[area] += nSeats - nTotalSeats[area];
-        nTotalSeats[area] = nSeats;
-    }
+        clearTransactions();
 
+        theatreState = resetState;
+    }
     /**
      * Get the number of free seats in the given area.
      *
@@ -508,7 +310,7 @@
      */
     public int getNFreeSeats(int area)
     {
-        return nFreeSeats[area];
+        return theatreState.freeSeats[area];
     }
 
     /**
@@ -519,7 +321,7 @@
      */
     public int getNTotalSeats(int area)
     {
-        return nTotalSeats[area];
+        return theatreState.totalSeats[area];
     }
 
     /**
@@ -530,7 +332,7 @@
      */
     public int getNBookedSeats(int area)
     {
-        return nBookedSeats[area];
+        return theatreState.bookedSeats[area];
     }
 
     /**
@@ -541,31 +343,15 @@
      */
     public int getNPreparedSeats(int area)
     {
-        return nPreparedSeats[area];
+        if (isLocked()) {
+            TheatreState childState = getPreparedState();
+            return childState.bookedSeats[area] - theatreState.bookedSeats[area];
+        } else {
+            return 0;
+        }
     }
 
     /**
-     * Get the number of committed seats in the given area.
-     *
-     * @param area The area of interest
-     * @return The number of committed seats
-     */
-    public int getNCommittedSeats(int area)
-    {
-        return nCommittedSeats[area];
-    }
-
-    /**
-     * Get the number of compensatable seats in the given area.
-     *
-     * @return The number of compensatable seats
-     */
-    public int getNCompensatableSeats(int area)
-    {
-        return nCompensatableSeats[area];
-    }
-
-    /**
      * Determine the autoCommit status of the instance.
      *
      * @return true if autoCommit mode is active, false otherwise
@@ -623,106 +409,73 @@
         isCommit = commit;
     }
 
+    /*****************************************************************************/
+    /* Implementation of inherited abstract sate management API                  */
+    /*****************************************************************************/
+
     /**
-     * (re-)initialise the instance data structures deleting any previously saved
-     * transaction state.
+     * identify the name of file used to store the current service state
+      * @return the name of the file used to store the current service state
      */
-    public void setToDefault()
-    {
-        setToDefault(true);
+    public String getStateFilename() {
+        return STATE_FILENAME;
     }
+
     /**
-     * (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
+     * identify the name of file used to store the shadow service state
+      * @return the name of the file used to store the shadow service state
      */
-    public void setToDefault(boolean deleteSavedState)
+    public String getShadowStateFilename() {
+        return SHADOW_STATE_FILENAME;
+    }
+
+    /*****************************************************************************/
+    /* Recovery methods maintaining consistency of local and  WSAT/WSBA state    */
+    /*****************************************************************************/
+
+    /**
+     * called by the AT recovery module when an AT participant is recovered from a log record
+     */
+    public void recovered(TheatreParticipantAT participant)
     {
-        nTotalSeats = new int[NUM_SEAT_AREAS];
-        nFreeSeats = new int[NUM_SEAT_AREAS];
-        nBookedSeats = new int[NUM_SEAT_AREAS];
-        nPreparedSeats = new int[NUM_SEAT_AREAS];
-        nCommittedSeats = new int[NUM_SEAT_AREAS];
-        nCompensatableSeats = new int[NUM_SEAT_AREAS];
-        for (int i = 0; i < NUM_SEAT_AREAS; i++)
-        {
-            nTotalSeats[i] = DEFAULT_SEATING_CAPACITY;
-            nFreeSeats[i] = nTotalSeats[i];
-            nBookedSeats[i] = 0;
-            nPreparedSeats[i] = 0;
-            nCommittedSeats[i] = 0;
-            nCompensatableSeats[i] = 0;
+        // if this AT participant matches the prepared TX id then we need to leave it prepared and locked
+        // at the end of scanning so it can be completed at commit time
+        if (isLockID(participant.txID)) {
+            rollbackPreparedTx = false;
         }
-        compensatableTransactions = new Hashtable();
-        preparedTransactions = new Hashtable();
-        unpreparedTransactions = new Hashtable();
-        autoCommitMode = true;
-        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.
+     * called by the BA recovery module when an AT participant is recovered from a log record
      */
-    public synchronized static TheatreManager getSingletonInstance()
+    public void recovered(TheatreParticipantBA participant)
     {
-        if (singletonInstance == null)
-        {
-            singletonInstance = new TheatreManager();
+        // if this AT participant matches the prepared TX id then we roll it forward here by calling
+        // confirmCompleted so once again we don't need to roll back the prepared state
+        if (isLockID(participant.txID)) {
+            participant.confirmCompleted(true);
+            rollbackPreparedTx = false;
         }
+    }
 
-        return singletonInstance;
+    public void recoveryScanCompleted(int txType)
+    {
+        super.recoveryScanCompleted(txType);
+        if (completedScans == TX_TYPE_BOTH && rollbackPreparedTx) {
+            rollbackSeats(getLockID());
+        }
     }
 
+    /*****************************************************************************/
+    /* Private implementation                                                    */
+    /*****************************************************************************/
+
     /**
      * A singleton instance of this class.
      */
     private static TheatreManager singletonInstance;
 
-    /*
-     * The following arrays are indexed by seating type.
-     *
-     * nTotalSeats = ( nFreeSeats + nBookedSeats + nPreparedSeats )
-     */
-
     /**
-     * The total seating capacity of each area.
-     */
-    private int[] nTotalSeats;
-
-    /**
-     * The number of free seats in each area.
-     */
-    private int[] nFreeSeats;
-
-    /**
-     * The number of booked seats in each area.
-     * <p/>
-     * Note: This may exceed the total size of the area
-     */
-    private int[] nBookedSeats;
-
-    /**
-     * The number of prepared (promised) seats in each area.
-     */
-    private int[] nPreparedSeats;
-
-    /**
-     * The number of committed seats in each area.
-     */
-    private int[] nCommittedSeats;
-
-    /**
-     * The number of compensatable seats in each area.
-     */
-    private int[] nCompensatableSeats;
-
-    /**
      * The auto commit mode.
      * <p/>
      * true = automatically commit, false = manually commit
@@ -745,199 +498,48 @@
     private boolean isCommit;
 
     /**
-     * The transactions we know about but which have not been prepared.
+     * Flag which determines whether we have to roll back any prepared changes to the server. We roll back
+     * changes for an AT or BA participant if there is no associated log record because the participant never
+     * prepared or completed, respectvely.. If we see a log record for an AT participant we leave the prepared
+     * state behind since it will
      */
-    private Hashtable unpreparedTransactions;
+    private boolean rollbackPreparedTx;
 
     /**
-     * The transactions we know about and are prepared to commit.
+     * the latest version of the theatre state which includes a version id
+     * this state object is always stored on disk in the theatre state file
+     * a prepared version of a derived child state may also exist on disk in the
+     * theatre shadow state file
      */
-    private Hashtable preparedTransactions;
+    private TheatreState theatreState;
 
     /**
-     * The transactions we know about and are prepared to compensate.
+     * Create and initialise a new TheatreManager instance.
      */
-    private Hashtable compensatableTransactions;
-
-    /**
-     * Constant (array index) used for the seating area CIRCLE.
-     */
-    public static final int CIRCLE = 0;
-
-    /**
-     * Constant (array index) used for the seating area STALLS.
-     */
-    public static final int STALLS = 1;
-
-    /**
-     * Constant (array index) used for the seating area BALCONY.
-     */
-    public static final int BALCONY = 2;
-
-    /**
-     * The total number (array size) of seating areas.
-     */
-    public static final int NUM_SEAT_AREAS = 3;
-
-    /**
-     * 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 = "theatreManagerState";
-
-    /**
-     * the name of the file used 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()
+    private TheatreManager()
     {
-        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()) {
+        TheatreState restoredState = restoreState();
+        if (restoredState == null) {
+            // we need to create a new initial state and persist it to disk
+            restoredState = TheatreState.initialState();
+            Object txId = "initialisation-transaction-" + System.currentTimeMillis();
             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);
+                writeShadowState(txId, restoredState);
+                commitShadowState(txId);
+            } catch (IOException e) {
+                clearShadowState(txId);
+                System.out.println("error : unable to initialise theatre manager state " + e);
             }
-        } else {
-            System.out.println("Starting with default restaurant manager state");
         }
-    }
+        theatreState = restoredState;
 
-    /**
-     * 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);
+        preparation = new Object();
+        // we will roll back any locally prepared changes to web service state unless we discover that
+        // they are needed during recovery
+        rollbackPreparedTx = isLocked();
 
-        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);
+        isCommit = true;
+        autoCommitMode = true;
+        isPreparationWaiting = false;
     }
-
-    /**
-     * 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();
-            nCompensatableSeats[i] = ois.readInt();
-        }
-        compensatableTransactions = 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);
-            }
-            compensatableTransactions.put(name, counts);
-            name = (String)ois.readObject();
-        }
-        preparedTransactions = new Hashtable();
-        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]);
-            oos.writeInt(nCompensatableSeats[i]);
-        }
-        Enumeration keys = compensatableTransactions.keys();
-        while (keys.hasMoreElements()) {
-            String name = (String)keys.nextElement();
-            Integer[] counts = (Integer[]) compensatableTransactions.get(name);
-            oos.writeObject(name);
-            for (int i = 0; i < NUM_SEAT_AREAS; i++) {
-                oos.writeInt(counts[i].intValue());
-            }
-        }
-        oos.writeObject("");
-        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	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreParticipantAT.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -43,6 +43,9 @@
  */
 public class TheatreParticipantAT implements Durable2PCParticipant, Serializable
 {
+    /************************************************************************/
+    /* public methods                                                       */
+    /************************************************************************/
     /**
      * Participant instances are related to transaction instances
      * in a one to one manner.
@@ -56,6 +59,9 @@
         this.txID = txID;
     }
 
+    /************************************************************************/
+    /* Durable2PCParticipant methods                                        */
+    /************************************************************************/
     /**
      * Invokes the prepare step of the business logic,
      * reporting activity and outcome.
@@ -87,7 +93,6 @@
         }
         else
         {
-            getTheatreManager().cancelSeats(txID) ;
             getTheatreView().addMessage("Prepare failed (not enough seats?) Returning 'Aborted'\n");
             getTheatreView().updateFields();
             return new Aborted();
@@ -110,19 +115,10 @@
 
         getTheatreView().addMessage("id:" + txID + ". Commit called on participant: " + this.getClass().toString());
 
-        boolean success = getTheatreManager().commitSeats(txID);
+        getTheatreManager().commitSeats(txID);
 
-        // Log the outcome
+        getTheatreView().addMessage("Theatre tickets committed\n");
 
-        if (success)
-        {
-            getTheatreView().addMessage("Theatre tickets committed\n");
-        }
-        else
-        {
-            getTheatreView().addMessage("Something went wrong (Transaction not registered?)\n");
-        }
-
         getTheatreView().updateFields();
     }
 
@@ -142,35 +138,13 @@
 
         getTheatreView().addMessage("id:" + txID + ". Rollback called on participant: " + this.getClass().toString());
 
-        boolean success = getTheatreManager().cancelSeats(txID);
+        getTheatreManager().rollbackSeats(txID);
 
-        // Log the outcome
+        getTheatreView().addMessage("Theatre booking cancelled\n");
 
-        if (success)
-        {
-            getTheatreView().addMessage("Theatre booking cancelled\n");
-        }
-        else
-        {
-            getTheatreView().addMessage("Something went wrong (Transaction not registered?)\n");
-        }
-
         getTheatreView().updateFields();
     }
 
-    /**
-     * Shortcut method which combines the prepare
-     * and commit steps in a single operation.
-     *
-     * @throws WrongStateException
-     * @throws SystemException
-     */
-    public void commitOnePhase() throws WrongStateException, SystemException
-    {
-        prepare();
-        commit();
-    }
-
     public void unknown() throws SystemException
     {
         // used for calbacks during crash recovery. This impl is not recoverable
@@ -181,6 +155,9 @@
         // used for calbacks during crash recovery. This impl is not recoverable
     }
 
+    /************************************************************************/
+    /* private implementation                                               */
+    /************************************************************************/
     /**
      * Id for the transaction which this participant instance relates to.
      * Set by the service (via contrtuctor) at enrolment time, this value

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreParticipantBA.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreParticipantBA.java	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreParticipantBA.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -30,7 +30,7 @@
 package com.jboss.jbosstm.xts.demo.services.theatre;
 
 import com.arjuna.wst.*;
-import com.arjuna.ats.arjuna.common.Uid;
+import com.arjuna.wst11.ConfirmCompletedParticipant;
 
 import java.io.Serializable;
 
@@ -42,21 +42,24 @@
  * @author Jonathan Halliday (jonathan.halliday at arjuna.com)
  * @version $Revision: 1.3 $
  */
-public class TheatreParticipantBA implements BusinessAgreementWithParticipantCompletionParticipant, Serializable
+public class TheatreParticipantBA implements
+        BusinessAgreementWithParticipantCompletionParticipant,
+        ConfirmCompletedParticipant,
+        Serializable
 {
+    /************************************************************************/
+    /* public methods                                                       */
+    /************************************************************************/
     /**
      * Participant instances are related to business method calls
      * in a one to one manner.
      *
      * @param txID       uniq id String for the transaction instance.
-     * @param how_many   seats to book/compensate.
-     * @param which_area of the theatre the seats are in.
      */
     public TheatreParticipantBA(String txID, int how_many, int which_area)
     {
         // we need to save the txID for later use when logging
         this.txID = txID;
-        // we also need the business paramater(s) in case of compensation
         this.seatCount = how_many;
         this.seatingArea = which_area;
     }
@@ -69,20 +72,17 @@
      * @throws SystemException never in this implementation.
      */
 
+    /************************************************************************/
+    /* BusinessAgreementWithParticipantCompletionParticipant methods        */
+    /************************************************************************/
     public void close() throws WrongStateException, SystemException
     {
-        // let the manager know that this activity no longer requires the option of compensation
+        // nothing to do here as the seats are already booked
 
         System.out.println("TheatreParticipantBA.close");
 
-        if (!getTheatreManager().closeSeats(txID)) {
-            // throw a WrongStateException to indicate that we were not expecting a close
-            System.out.println("TheatreParticipantBA.close : not expecting a close for BA participant " + txID);
+        getTheatreView().addMessage("id:" + txID + ". Close called on participant: " + this.getClass());
 
-            throw new WrongStateException("Unexpected close for BA participant " + txID);
-        }
-
-        getTheatreView().addMessage("id:" + txID + ". Close called on participant: " + this.getClass());
         getTheatreView().updateFields();
     }
 
@@ -101,14 +101,10 @@
 
         System.out.println("TheatreParticipantBA.cancel");
 
-        if (!getTheatreManager().cancelSeats(txID)) {
-            // throw a WrongStateException to indicate that we were not expecting a close
-            System.out.println("TheatreParticipantBA.cancel : not expecting a cancel for BA participant " + txID);
+        getTheatreManager().rollbackSeats(txID);
 
-            throw new WrongStateException("Unexpected cancel for BA participant " + txID);
-        }
+        getTheatreView().addMessage("id:" + txID + ". Cancel called on participant: " + this.getClass().toString());
 
-        getTheatreView().addMessage("id:" + txID + ". Cancel called on participant: " + this.getClass().toString());
         getTheatreView().updateFields();
     }
 
@@ -129,21 +125,19 @@
 
         getTheatreView().updateFields();
 
-        // tell the manager to compensate
+        // we perform the compensation by preparing and then committing a change which
+        // decrements the bookings
 
-        try {
-            if (!getTheatreManager().compensateSeats(txID)) {
-                // throw a WrongStateException to indicate that we were not expecting a close
-                System.out.println("TheatreParticipantBA.compensate : not expecting a compensate for BA participant " + txID);
+        String compensationTxID = txID + "-compensation";
 
-                throw new WrongStateException("Unexpected compensate for BA participant " + txID);
-            }
-        } catch (FaultedException fe) {
-            getTheatreView().addMessage("id:" + txID + ". FaultedException when compensating participant: " + this.getClass().toString());
+        getTheatreManager().bookSeats(compensationTxID, -seatCount, seatingArea);
 
-            getTheatreView().updateFields();
-            throw fe;
+        if (!getTheatreManager().prepareSeats(compensationTxID)) {
+            getTheatreView().addMessage("id:" + txID + ". Failed to compensate participant: " + this.getClass().toString());
+
+            throw new FaultedException("Failed to compensate participant " + txID);
         }
+        getTheatreManager().commitSeats(compensationTxID);
 
         getTheatreView().addMessage("id:" + txID + ". Compensated participant: " + this.getClass().toString());
 
@@ -177,8 +171,30 @@
         getTheatreView().updateFields();
     }
 
+    /************************************************************************/
+    /* ConfirmCompletedParticipant methods                                  */
+    /************************************************************************/
 
     /**
+     * method called to perform commit or rollback of prepared changes to the underlying manager state after
+     * the participant recovery record has been written
+     *
+     * @param confirmed true if the log record has been written and changes should be rolled forward and false
+     * if it has not been written and changes should be rolled back
+     */
+
+    public void confirmCompleted(boolean confirmed) {
+        if (confirmed) {
+            getTheatreManager().commitSeats(txID);
+        } else {
+            getTheatreManager().rollbackSeats(txID);
+        }
+    }
+
+    /************************************************************************/
+    /* private implementation                                               */
+    /************************************************************************/
+    /**
      * Id for the transaction which this participant instance relates to.
      * Set by the service (via contrtuctor) at enrolment time, this value
      * is used in informational log messages.
@@ -202,4 +218,5 @@
     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	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreServiceAT.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -40,8 +40,6 @@
 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
@@ -58,26 +56,6 @@
 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.
@@ -121,6 +99,7 @@
 
         theatreView.addMessage("id:" + transactionId.toString() + ". Received a theatre booking request for " + how_many + " seats in area " + which_area);
 
+        // invoke the backend business logic:
         TheatreManager.getSingletonInstance().bookSeats(transactionId, how_many, which_area);
 
         theatreView.addMessage("Request complete\n");

Modified: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreServiceBA.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreServiceBA.java	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreServiceBA.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -96,49 +96,76 @@
         theatreView.addPrepareMessage("id:" + transactionId + ". Received a theatre booking request for " + how_many + " seats in area " + which_area);
         theatreView.updateFields();
 
+        if (theatreManager.knowsAbout(transactionId)) {
+            // hmm, this means we have already completed changes in this transaction and are awaiting a close
+            //or compensate request. this service does not support repeated requests in the same activity so
+            // we fail this request.
+
+            theatreView.addMessage("id:" + transactionId + ". Participant already enrolled!");
+            theatreView.updateFields();
+            System.err.println("bookSeats: request failed");
+            return false;
+        }
+
+        TheatreParticipantBA theatreParticipant = new TheatreParticipantBA(transactionId, how_many, which_area);
+        BAParticipantManager participantManager;
+
+        // enlist the Participant for this service:
+        try
+        {
+            participantManager = activityManager.enlistForBusinessAgreementWithParticipantCompletion(theatreParticipant, "org.jboss.jbossts.xts-demo:theatreBA:" + new Uid().toString());
+        }
+        catch (Exception e)
+        {
+            theatreView.addMessage("id:" + transactionId + ". Participant enrolement failed");
+            theatreManager.rollbackSeats(transactionId);
+            System.err.println("bookSeats: Participant enrolement failed");
+            e.printStackTrace(System.err);
+            return false;
+        }
+
+        // invoke the backend business logic:
         theatreManager.bookSeats(transactionId, how_many, which_area);
 
+        // this service employs the participant completion protocol which means it decides when it wants to
+        // commit local changes. so we prepare and commit those changes now. if any other participant fails
+        // or the client decides to cancel we can rely upon being told to compensate.
+
         if (theatreManager.prepareSeats(transactionId))
         {
             theatreView.addMessage("id:" + transactionId + ". Seats prepared, trying to commit and enlist compensation Participant");
             theatreView.updateFields();
 
-            TheatreParticipantBA theatreParticipant = new TheatreParticipantBA(transactionId, how_many, which_area);
-            // enlist the Participant for this service:
-            BAParticipantManager participantManager = null;
             try
             {
-                participantManager = activityManager.enlistForBusinessAgreementWithParticipantCompletion(theatreParticipant, "org.jboss.jbossts.xts-demo:restaurantBA:" + new Uid().toString());
+                // tell the participant manager we have finished our work
+                // this will call back to the participant once a compensation recovery record has been written
+                // allowing it to commit or roll back the theatre manager
+                participantManager.completed();
             }
             catch (Exception e)
             {
-                theatreView.addMessage("id:" + transactionId + ". Participant enrolement failed");
-                theatreManager.cancelSeats(transactionId);
-                System.err.println("bookSeats: Participant enrolement failed");
+                System.err.println("bookSeats: 'completed' callback failed");
+                theatreManager.rollbackSeats(transactionId);
                 e.printStackTrace(System.err);
                 return false;
             }
-
-            // finish the booking in the backend ensuring it is compensatable:
-            theatreManager.commitSeats(transactionId, true);
-
+        }
+        else
+        {
+            theatreView.addMessage("id:" + transactionId + ". Failed to reserve seats. Cancelling.");
+            theatreView.updateFields();
             try
             {
-                participantManager.completed();
+                // tell the participant manager we cannot complete. this will force the activity to fail
+                participantManager.cannotComplete();
             }
             catch (Exception e)
             {
-                System.err.println("bookSeats: 'completed' callback failed");
-                theatreManager.cancelSeats(transactionId);
+                System.err.println("bookSeats: 'cannotComplete' callback failed");
                 e.printStackTrace(System.err);
                 return false;
             }
-        }
-        else
-        {
-            theatreView.addMessage("id:" + transactionId + ". Failed to reserve seats. Cancelling.");
-            theatreManager.cancelSeats(transactionId);
-            theatreView.updateFields();
             return false;
         }
 

Added: labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreState.java
===================================================================
--- labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreState.java	                        (rev 0)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreState.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -0,0 +1,94 @@
+package com.jboss.jbosstm.xts.demo.services.theatre;
+
+import com.jboss.jbosstm.xts.demo.services.state.ServiceState;
+
+import java.util.Arrays;
+
+import static com.jboss.jbosstm.xts.demo.services.theatre.TheatreConstants.*;
+
+/**
+ * An object which models the state of a restaurant identifying the number of free and
+ * booked seats and the total available
+ */
+public class TheatreState extends ServiceState {
+    int[] totalSeats;
+    int[] bookedSeats;
+    int[] freeSeats;
+
+    /**
+     * create a new initial theatre state
+     * @param count
+     * @return
+     */
+    public static TheatreState initialState()
+    {
+        return new TheatreState();
+    }
+    /**
+     * derive a child theatre state from this state
+     * @return
+     */
+    public TheatreState derivedState()
+    {
+        return new TheatreState(this);
+    }
+
+    public String toString()
+    {
+        StringBuilder builder = new StringBuilder();
+        builder.append("TheatreState{version=");
+        builder.append(version);
+        builder.append(", totalSeats=[circle=");
+        builder.append(totalSeats[CIRCLE]);
+        builder.append(", STALLS=");
+        builder.append(totalSeats[STALLS]);
+        builder.append(", BALCONY=");
+        builder.append(totalSeats[BALCONY]);
+        builder.append("], bookedSeats=[circle=");
+        builder.append(bookedSeats[CIRCLE]);
+        builder.append(", STALLS=");
+        builder.append(bookedSeats[STALLS]);
+        builder.append(", BALCONY=");
+        builder.append(bookedSeats[BALCONY]);
+        builder.append("], freeSeats=[circle=");
+        builder.append(freeSeats[CIRCLE]);
+        builder.append(", STALLS=");
+        builder.append(freeSeats[STALLS]);
+        builder.append(", BALCONY=");
+        builder.append(freeSeats[BALCONY]);
+        builder.append("]}");
+        return builder.toString();
+    }
+    /**
+     * create a new initial restaurant state
+     *
+     * @param totalSeats
+     */
+    private TheatreState()
+    {
+        this.totalSeats = new int[NUM_SEAT_AREAS];
+        this.bookedSeats = new int[NUM_SEAT_AREAS];
+        this.freeSeats = new int[NUM_SEAT_AREAS];
+        for (int i = 0; i < NUM_SEAT_AREAS; i++) {
+            totalSeats[i] = DEFAULT_SEATING_CAPACITY;
+            bookedSeats[i] = 0;
+            freeSeats[i] = DEFAULT_SEATING_CAPACITY;
+        }
+    }
+
+    /**
+     * create a restaurant state with a given number of bookings and a specific version
+     *
+     * @param totalSeats
+     * @param bookedSeats
+     * @param version
+     */
+    private TheatreState(TheatreState parent)
+    {
+        super(parent);
+        this.totalSeats =  Arrays.copyOf(parent.totalSeats, NUM_SEAT_AREAS);
+        this.bookedSeats =  Arrays.copyOf(parent.bookedSeats, NUM_SEAT_AREAS);
+        this.freeSeats =  Arrays.copyOf(parent.freeSeats, NUM_SEAT_AREAS);
+    }
+
+}

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	2010-09-20 08:16:05 UTC (rev 35188)
+++ labs/jbosstm/trunk/XTS/demo/src/com/jboss/jbosstm/xts/demo/services/theatre/TheatreView.java	2010-09-20 10:11:03 UTC (rev 35189)
@@ -29,6 +29,7 @@
 
 package com.jboss.jbosstm.xts.demo.services.theatre;
 
+import static com.jboss.jbosstm.xts.demo.services.theatre.TheatreConstants.*;
 import java.io.Serializable;
 
 /**
@@ -138,17 +139,17 @@
         jLabel25.setFont(new java.awt.Font("Dialog", 1, 14));
         jPanel1.add(jLabel25);
 
-        jLabelNBookedSeatsCircle.setText(Integer.toString(theatreManager.getNBookedSeats(theatreManager.CIRCLE)));
+        jLabelNBookedSeatsCircle.setText(Integer.toString(theatreManager.getNBookedSeats(CIRCLE)));
         jLabelNBookedSeatsCircle.setForeground(java.awt.Color.gray);
         jLabelNBookedSeatsCircle.setFont(new java.awt.Font("Dialog", 0, 14));
         jPanel1.add(jLabelNBookedSeatsCircle);
 
-        jLabelNConfirmedSeatsCircle.setText(Integer.toString(theatreManager.getNCommittedSeats(theatreManager.CIRCLE)));
+        jLabelNConfirmedSeatsCircle.setText(Integer.toString(0));
         jLabelNConfirmedSeatsCircle.setForeground(new java.awt.Color(0, 51, 204));
         jLabelNConfirmedSeatsCircle.setFont(new java.awt.Font("Dialog", 0, 14));
         jPanel1.add(jLabelNConfirmedSeatsCircle);
 
-        jLabelNFreeSeatsCircle.setText(Integer.toString(theatreManager.getNFreeSeats(theatreManager.CIRCLE)));
+        jLabelNFreeSeatsCircle.setText(Integer.toString(theatreManager.getNFreeSeats(CIRCLE)));
         jLabelNFreeSeatsCircle.setForeground(new java.awt.Color(0, 153, 0));
         jLabelNFreeSeatsCircle.setFont(new java.awt.Font("Dialog", 0, 14));
         jPanel1.add(jLabelNFreeSeatsCircle);
@@ -157,17 +158,17 @@
         jLabel35.setFont(new java.awt.Font("Dialog", 1, 14));
         jPanel1.add(jLabel35);
 
-        jLabelNBookedSeatsStalls.setText(Integer.toString(theatreManager.getNBookedSeats(theatreManager.STALLS)));
+        jLabelNBookedSeatsStalls.setText(Integer.toString(theatreManager.getNBookedSeats(STALLS)));
         jLabelNBookedSeatsStalls.setForeground(java.awt.Color.gray);
         jLabelNBookedSeatsStalls.setFont(new java.awt.Font("Dialog", 0, 14));
         jPanel1.add(jLabelNBookedSeatsStalls);
 
-        jLabelNConfirmedSeatsStalls.setText(Integer.toString(theatreManager.getNCommittedSeats(theatreManager.STALLS)));
+        jLabelNConfirmedSeatsStalls.setText(Integer.toString(0));
         jLabelNConfirmedSeatsStalls.setForeground(new java.awt.Color(0, 51, 204));
         jLabelNConfirmedSeatsStalls.setFont(new java.awt.Font("Dialog", 0, 14));
         jPanel1.add(jLabelNConfirmedSeatsStalls);
 
-        jLabelNFreeSeatsStalls.setText(Integer.toString(theatreManager.getNFreeSeats(theatreManager.STALLS)));
+        jLabelNFreeSeatsStalls.setText(Integer.toString(theatreManager.getNFreeSeats(STALLS)));
         jLabelNFreeSeatsStalls.setForeground(new java.awt.Color(0, 153, 0));
         jLabelNFreeSeatsStalls.setFont(new java.awt.Font("Dialog", 0, 14));
         jPanel1.add(jLabelNFreeSeatsStalls);
@@ -176,17 +177,17 @@
         jLabel28.setFont(new java.awt.Font("Dialog", 1, 14));
         jPanel1.add(jLabel28);
 
-        jLabelNBookedSeatsBalcony.setText(Integer.toString(theatreManager.getNBookedSeats(theatreManager.BALCONY)));
+        jLabelNBookedSeatsBalcony.setText(Integer.toString(theatreManager.getNBookedSeats(BALCONY)));
         jLabelNBookedSeatsBalcony.setForeground(java.awt.Color.gray);
         jLabelNBookedSeatsBalcony.setFont(new java.awt.Font("Dialog", 0, 14));
         jPanel1.add(jLabelNBookedSeatsBalcony);
 
-        jLabelNConfirmedSeatsBalcony.setText(Integer.toString(theatreManager.getNCommittedSeats(theatreManager.BALCONY)));
+        jLabelNConfirmedSeatsBalcony.setText(Integer.toString(0));
         jLabelNConfirmedSeatsBalcony.setForeground(new java.awt.Color(51, 0, 204));
         jLabelNConfirmedSeatsBalcony.setFont(new java.awt.Font("Dialog", 0, 14));
         jPanel1.add(jLabelNConfirmedSeatsBalcony);
 
-        jLabelNFreeSeatsBalcony.setText(Integer.toString(theatreManager.getNFreeSeats(theatreManager.BALCONY)));
+        jLabelNFreeSeatsBalcony.setText(Integer.toString(theatreManager.getNFreeSeats(BALCONY)));
         jLabelNFreeSeatsBalcony.setForeground(new java.awt.Color(0, 153, 0));
         jLabelNFreeSeatsBalcony.setFont(new java.awt.Font("Dialog", 0, 14));
         jPanel1.add(jLabelNFreeSeatsBalcony);
@@ -223,7 +224,7 @@
         jLabel18.setFont(new java.awt.Font("Dialog", 1, 14));
         jPanel2.add(jLabel18);
 
-        jLabelNTotalSeatsCircle.setText(Integer.toString(theatreManager.getNTotalSeats(theatreManager.CIRCLE)));
+        jLabelNTotalSeatsCircle.setText(Integer.toString(theatreManager.getNTotalSeats(CIRCLE)));
         jLabelNTotalSeatsCircle.setForeground(java.awt.Color.darkGray);
         jLabelNTotalSeatsCircle.setFont(new java.awt.Font("Dialog", 0, 14));
         jPanel2.add(jLabelNTotalSeatsCircle);
@@ -232,7 +233,7 @@
         jLabel19.setFont(new java.awt.Font("Dialog", 1, 14));
         jPanel2.add(jLabel19);
 
-        jLabelNTotalSeatsStalls.setText(Integer.toString(theatreManager.getNTotalSeats(theatreManager.STALLS)));
+        jLabelNTotalSeatsStalls.setText(Integer.toString(theatreManager.getNTotalSeats(STALLS)));
         jLabelNTotalSeatsStalls.setForeground(java.awt.Color.darkGray);
         jLabelNTotalSeatsStalls.setFont(new java.awt.Font("Dialog", 0, 14));
         jPanel2.add(jLabelNTotalSeatsStalls);
@@ -241,13 +242,13 @@
         jLabel20.setFont(new java.awt.Font("Dialog", 1, 14));
         jPanel2.add(jLabel20);
 
-        jLabelNTotalSeatsBalcony.setText(Integer.toString(theatreManager.getNTotalSeats(theatreManager.BALCONY)));
+        jLabelNTotalSeatsBalcony.setText(Integer.toString(theatreManager.getNTotalSeats(BALCONY)));
         jLabelNTotalSeatsBalcony.setForeground(java.awt.Color.darkGray);
         jLabelNTotalSeatsBalcony.setFont(new java.awt.Font("Dialog", 0, 14));
         jPanel2.add(jLabelNTotalSeatsBalcony);
 
         jTextFieldNewNTotalSeats.setFont(new java.awt.Font("Dialog", 0, 18));
-        jTextFieldNewNTotalSeats.setText(Integer.toString(theatreManager.getNTotalSeats(theatreManager.CIRCLE)));
+        jTextFieldNewNTotalSeats.setText(Integer.toString(theatreManager.getNTotalSeats(CIRCLE)));
         jPanel2.add(jTextFieldNewNTotalSeats);
 
         jButtonSetCircle.setFont(new java.awt.Font("Dialog", 0, 14));
@@ -358,7 +359,7 @@
      */
     private void jButtonResetFieldsActionPerformed(java.awt.event.ActionEvent evt)
     {//GEN-FIRST:event_jButtonResetFieldsActionPerformed
-        theatreManager.setToDefault();
+        theatreManager.reset();
         updateFields();
     }//GEN-LAST:event_jButtonResetFieldsActionPerformed
 
@@ -427,8 +428,8 @@
     {//GEN-FIRST:event_jButtonSetCircleActionPerformed
         String strNSeats = jTextFieldNewNTotalSeats.getText();
 
-        theatreManager.newCapacity(theatreManager.CIRCLE, Integer.parseInt(strNSeats));
-        int nFreeSeats = theatreManager.getNFreeSeats(theatreManager.CIRCLE);
+        theatreManager.reset();
+        int nFreeSeats = theatreManager.getNFreeSeats(CIRCLE);
 
         jLabelNTotalSeatsCircle.setText(strNSeats);
         jLabelNFreeSeatsCircle.setText(Integer.toString(nFreeSeats));
@@ -441,8 +442,8 @@
     {//GEN-FIRST:event_jButtonSetSallsActionPerformed
         String strNSeats = jTextFieldNewNTotalSeats.getText();
 
-        theatreManager.newCapacity(theatreManager.STALLS, Integer.parseInt(strNSeats));
-        int nFreeSeats = theatreManager.getNFreeSeats(theatreManager.STALLS);
+        theatreManager.reset();
+        int nFreeSeats = theatreManager.getNFreeSeats(STALLS);
 
         jLabelNTotalSeatsStalls.setText(strNSeats);
         jLabelNFreeSeatsStalls.setText(Integer.toString(nFreeSeats));
@@ -455,8 +456,8 @@
     {//GEN-FIRST:event_jButtonSetBalconyActionPerformed
         String strNSeats = jTextFieldNewNTotalSeats.getText();
 
-        theatreManager.newCapacity(theatreManager.BALCONY, Integer.parseInt(strNSeats));
-        int nFreeSeats = theatreManager.getNFreeSeats(theatreManager.BALCONY);
+        theatreManager.reset();
+        int nFreeSeats = theatreManager.getNFreeSeats(BALCONY);
 
         jLabelNTotalSeatsBalcony.setText(strNSeats);
         jLabelNFreeSeatsBalcony.setText(Integer.toString(nFreeSeats));
@@ -501,19 +502,19 @@
      */
     public void updateFields()
     {
-        jLabelNTotalSeatsCircle.setText(Integer.toString(theatreManager.getNTotalSeats(theatreManager.CIRCLE)));
-        jLabelNTotalSeatsStalls.setText(Integer.toString(theatreManager.getNTotalSeats(theatreManager.STALLS)));
-        jLabelNTotalSeatsBalcony.setText(Integer.toString(theatreManager.getNTotalSeats(theatreManager.BALCONY)));
+        jLabelNTotalSeatsCircle.setText(Integer.toString(theatreManager.getNTotalSeats(CIRCLE)));
+        jLabelNTotalSeatsStalls.setText(Integer.toString(theatreManager.getNTotalSeats(STALLS)));
+        jLabelNTotalSeatsBalcony.setText(Integer.toString(theatreManager.getNTotalSeats(BALCONY)));
         jTextFieldNewNTotalSeats.setText(jLabelNTotalSeatsCircle.getText());
-        jLabelNBookedSeatsCircle.setText(Integer.toString(theatreManager.getNBookedSeats(theatreManager.CIRCLE)));
-        jLabelNBookedSeatsStalls.setText(Integer.toString(theatreManager.getNBookedSeats(theatreManager.STALLS)));
-        jLabelNBookedSeatsBalcony.setText(Integer.toString(theatreManager.getNBookedSeats(theatreManager.BALCONY)));
-        jLabelNConfirmedSeatsCircle.setText(Integer.toString(theatreManager.getNCommittedSeats(theatreManager.CIRCLE)));
-        jLabelNConfirmedSeatsStalls.setText(Integer.toString(theatreManager.getNCommittedSeats(theatreManager.STALLS)));
-        jLabelNConfirmedSeatsBalcony.setText(Integer.toString(theatreManager.getNCommittedSeats(theatreManager.BALCONY)));
-        jLabelNFreeSeatsCircle.setText(Integer.toString(theatreManager.getNFreeSeats(theatreManager.CIRCLE)));
-        jLabelNFreeSeatsStalls.setText(Integer.toString(theatreManager.getNFreeSeats(theatreManager.STALLS)));
-        jLabelNFreeSeatsBalcony.setText(Integer.toString(theatreManager.getNFreeSeats(theatreManager.BALCONY)));
+        jLabelNBookedSeatsCircle.setText(Integer.toString(theatreManager.getNBookedSeats(CIRCLE)));
+        jLabelNBookedSeatsStalls.setText(Integer.toString(theatreManager.getNBookedSeats(STALLS)));
+        jLabelNBookedSeatsBalcony.setText(Integer.toString(theatreManager.getNBookedSeats(BALCONY)));
+        jLabelNConfirmedSeatsCircle.setText(Integer.toString(0));
+        jLabelNConfirmedSeatsStalls.setText(Integer.toString(0));
+        jLabelNConfirmedSeatsBalcony.setText(Integer.toString(0));
+        jLabelNFreeSeatsCircle.setText(Integer.toString(theatreManager.getNFreeSeats(CIRCLE)));
+        jLabelNFreeSeatsStalls.setText(Integer.toString(theatreManager.getNFreeSeats(STALLS)));
+        jLabelNFreeSeatsBalcony.setText(Integer.toString(theatreManager.getNFreeSeats(BALCONY)));
 
 
         //update fields related to interactive mode



More information about the jboss-svn-commits mailing list