[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