[jboss-svn-commits] JBL Code SVN: r25035 - in labs/jbosstm/workspace/resttx: docs and 8 other directories.

jboss-svn-commits at lists.jboss.org jboss-svn-commits at lists.jboss.org
Sun Feb 1 07:05:45 EST 2009


Author: mmusgrov
Date: 2009-02-01 07:05:44 -0500 (Sun, 01 Feb 2009)
New Revision: 25035

Added:
   labs/jbosstm/workspace/resttx/docs/jfdi-spec.txt
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/HttpResponseMapper.java
Modified:
   labs/jbosstm/workspace/resttx/build.xml
   labs/jbosstm/workspace/resttx/docs/readme.txt
   labs/jbosstm/workspace/resttx/jbossjta-properties.xml
   labs/jbosstm/workspace/resttx/pom.xml
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/client/TxTest.java
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/example/Participant.java
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/example/Work.java
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/HttpResponseException.java
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/NotFoundMapper.java
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/ResourceNotFoundException.java
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TMUnavailableException.java
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TMUnavailableMapper.java
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TransactionStatusException.java
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TransactionStatusMapper.java
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/resource/RESTRecord.java
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/resource/RESTRecordSetup.java
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/service/Coordinator.java
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/service/TMApplication.java
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/service/TransactionalParticipant.java
   labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/util/TxUtil.java
   labs/jbosstm/workspace/resttx/src/test/java/org/jboss/jbossts/rts/test/AppTest.java
   labs/jbosstm/workspace/resttx/src/test/resources/log4j.xml
Log:
Clean up the code and some missing functionality.



Modified: labs/jbosstm/workspace/resttx/build.xml
===================================================================
--- labs/jbosstm/workspace/resttx/build.xml	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/build.xml	2009-02-01 12:05:44 UTC (rev 25035)
@@ -15,8 +15,8 @@
     <pathelement location="${basedir}/target/rest-tx/WEB-INF/lib/commons-logging-1.1.jar"/>
     <pathelement location="${basedir}/target/test-classes"/>
     <fileset dir="${resteasy.dir}" >
-        <include name="jaxrs-api-1.0-beta-8.jar"/>
-        <include name="resteasy-jaxrs-1.0-beta-8.jar"/>
+        <include name="jaxrs-api-1.0-RC1.jar"/>
+        <include name="resteasy-jaxrs-1.0-RC1.jar"/>
         <include name="commons-httpclient-3.1.jar"/>
         <include name="commons-logging-1.0.4.jar"/>
         <include name="commons-codec-1.2.jar"/>
@@ -38,6 +38,7 @@
   <target name="run" depends="compile">
       <java classname="org.jboss.jbossts.rts.client.TxTest" fork="yes" dir=".">
           <classpath refid="classpath"/>
+          <sysproperty key="PORT" value="9099"/>
           <jvmarg value="-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5006" />
           <jvmarg value="-Xms128m" />
           <jvmarg value="-Xmx512M" />

Added: labs/jbosstm/workspace/resttx/docs/jfdi-spec.txt
===================================================================
--- labs/jbosstm/workspace/resttx/docs/jfdi-spec.txt	                        (rev 0)
+++ labs/jbosstm/workspace/resttx/docs/jfdi-spec.txt	2009-02-01 12:05:44 UTC (rev 25035)
@@ -0,0 +1,131 @@
+Here it is. I have some written notes on a few modifications we were going to make to create some other extended transaction models, but if we can get this and the acid one working that'd be enough for now  ;-)  As with the acid protocol, I'd do this one slightly differently this time round. Some day we should sit down and discuss  :-)
+
+Mark.
+
+----
+
+The JFDI transaction protocol is a forward-compensation based approach. It does not assume that resources are locked for the duration of the transaction. (Much of this model was subsequently incorporated into WS-LRA or WS-BP.
+
+Communication is initiated by the client in the form of HTTP GET, DELETE, POST, and PUT requests. GET is used to read data, DELETE is used to delete data, POST is used to create new data, and PUT is used to modify existing data. The server responds to requests by returning status codes, data, or both. Because REST transactions are stateless, each HTTP request must be accompanied by a URI that uniquely identifies the specific resource being requested. In addition to a URI, POST and PUT requests must be accompanied by an XML document containing data that conforms to the schema.
+
+Different HTTP status codes (and data) are returned by the server in response to requests:
+
+GET
+A successful GET returns status code 200 (OK) and XML data in the body.
+PUT
+A successful PUT returns status code 200 (OK) and XML data in the body.
+POST
+A successful POST returns status code 201 (created) and a Location header containing the URI of the newly created resource.
+DELETE
+A successful DELETE returns status code 204 only (no data).
+
+Error status codes include:
+
+400: Bad request
+The request could not be understood by the server due to malformed syntax.
+401: Unauthorized
+Invalid username and/or password, or insufficient privileges for the command.
+404: Not found
+Couldn't find the resource specified by the URI.
+410: Gone
+The requested resource is no longer available at the server and no forwarding address is known.
+
+Although POST is "unsafe" and we should use PUT because it is idempotent, it's not well supported by browsers.
+
+The long running action model (jfdi) is designed specifically for those business interactions that occur over a long duration. Within this model, an activity reflects business interactions: all work performed within the scope of an activity is required to be compensatable. Therefore, an activity’s work is either performed successfully or undone. How services perform their work and ensure it can be undone if compensation is required, are implementation choices and not exposed to the jfdi model. The jfdi model simply defines the triggers for compensation actions and the conditions under which those triggers are executed.
+
+As with most transaction models, jfdi is concerned only with ensuring participants obey the protocol necessary to make an activity compensatable; semantics of the business interactions are not part of jfdi model. Issues such as isolation of services between potentially conflicting activities and durability of service work are assumed to be implementation decisions. The coordination protocol used to ensure an activity is completed successfully or compensated is not two-phase and is intended to better model business-to-business interactions. Although this may result in non-atomic behaviour for the overall business activity, other activities may be started by the application or service to attempt to compensate in some other manner.
+Each jfdi is tied to the scope of an activity. This means that when the activity terminates, the jfdi coordination protocol will be automatically performed either to accept or compensate the work.
+
+In the jfdi model, each activity is bound to the scope of a compensation interaction. For example, when a user reserves a seat on a flight, the airline reservation centre may take an optimistic approach and actually book the seat and debit the users account, relying on the fact that most of their customers who reserve seats later book them; the compensation action for this activity would obviously be to un-book the seat and credit the user’s account. Work performed within the scope of a nested jfdi must remain compensatable until an enclosing activity informs the service(s) that it is no longer required. For example, consider the night-out reservation example mentioned earlier.
+
+As in any business interaction, application services may or may not be compensatable. Even the ability to compensate may be a transient capability of a service. A Compensator is the jfdi participant that operates on behalf of a service to undo the work it performs within the scope of an jfdi or to compensate for the fact that the original work could not be completed. How compensation is carried out will obviously be dependant upon the service; compensation work may be carried out by other jfdis which themselves have Compensators.
+
+Because jfdis may execute over a long period of time, compensation may have to occur at any time and be tolerant of failures. Consequently, Compensators may have to maintain information within a durable form
+
+When a service does work that may have to be later compensated within the scope of an jfdi, it enlists a Compensator participant with the jfdi coordinator. The Compensator will be invoked in the following way by the jfdi coordinator when the activity terminates:
+•    Success: the activity has completed successfully. If the activity is nested then Compensators may propagate themselves (or new Compensators) to the enclosing jfdi. Otherwise the Compensators are informed that the activity has terminated and they can perform any necessary cleanups.
+•    Fail: the activity has completed unsuccessfully. All Compensators that are registered with the jfdi will be invoked to perform compensation in the reverse order. The coordinator forgets about all Compensators that indicated they operated correctly. Otherwise, compensation may be attempted again (possibly after a period of time) or alternatively a compensation violation has occurred and must be logged.
+
+Each service is required to log sufficient information in order to ensure (with best effort) that compensation is possible. Each compensator (participant) or subordinate coordinator is responsible for ensuring that sufficient data is made durable in order to undo the jfdi in the event of failures. Interposition and check pointing of state allow the system to drive a consistent view of the outcome and recovery actions taken, but allowing always the possibility that recovery isn’t possible and must be logged or flagged for the administrator. In a large scale environment or in the presence of long term failures, recovery may not be automatic. As such, manual intervention may be necessary to restore an application’s consistency.
+
+Obviously jfdis may be used sequentially and concurrently, where the termination of a jfdi signals the start of some other unit of work within an application. However, jfdis are units of compensatable work and an application may have as many such units of work operating simultaneously as it needs to accomplish its tasks. Furthermore, the outcome of work within jfdis may determine how other jfdis are terminated. An application can be structured to so that jfdis are used to assemble units of compensatable work and then held in the active state while the application performs other work in the scope of different (concurrent or sequential) jfdis. Only when the right subset of work (jfdis) is arrived at by the application will that subset be confirmed; all other jfdis will be told to cancel (complete in a failure state).
+
+The jfdi transaction coordinator URL is:
+
+http://<machine>/jfdi-coordinator
+
+Performing a GET on that URL returns a list of all transactions know to the coordinator (active and recovery).
+
+Performing a GET on /jfdi-coordinator/recovery returns a list of transactions that are in recovery.
+
+Performing a GET on /jfdi-coordinator/active returns a list of inflight transaction ids, which can the be used below.
+
+Performing a DELETE on any of the jfdi-coordinator URLs will return a 401.
+
+Each client is expected to have a unique identity. We'll call that ClientID, which can be a URL too.
+
+Performing a POST on jfdi-coordinator/start?<ClientID> will start a new jfdi with a default timeout and return a URL of the form <machine>/jfdi-coordinator/<id>
+Performing a POST on jfdi-coordinator/start?<ClientID>#timeout will start a new transaction with the specified timeout and return a URL of the form <machine>/jfdi-coordinator/<id>
+
+If the transaction is terminated because of a timeout, the /jfdi-coordinator/<id> URL is deleted. All further invocations on the URL will return 404. The invoker can assume this was equivalent to a compensate operation.
+
+The jfdi model uses a presumed nothing protocol: the coordinator must communicate with Compensators in order to inform them of the jfdi activity. Every time a Compensator is enrolled with a jfdi, the coordinator must make information about it durable so that the Compensator can be contacted when the jfdi terminates, even in the event of subsequent failures. Compensators, clients and coordinators cannot make any presumption about the state of the global transaction without consulting the coordinator and all compensators, respectively.
+
+Performing a GET on /jfdi-coordinator/<TxId> returns 200 if the transaction is still active.
+
+Once the transaction terminates it is up to the implementation to retain information about the transaction for an indeterminate amount of time.
+
+Performing a GET on /jfdi-coordinator/completed/<TxId> returns 200 if the transaction completed successfully. 404 means it is not present.
+
+Performing a GET on /jfdi-coordinator/compensated/<TxId> returns 200 if the transaction compensated. 404 means it is not present.
+
+Performing a PUT on /jfdi-coordinator/<id>/close will trigger the successful completion of the transaction and all compensators will be dropped by the coordinator (the complete message will be sent to the compensators). Upon termination, the URL is implicitly deleted. If it no longer exists, then 404 will be returned. The invoker cannot know for sure whether the transaction completed or compensated without enlisting a participant.
+
+Each transaction is also uniquely identified.
+
+When a compensator registers with a transaction we need to return a unique handle (aka RecoveryCoordinator) so that it can be uniquely reasoned about later. So we use PUT:
+
+Performing a PUT on /jfdi-coordinator/<TxId> with the URL of the compensator (see below), will register the compensator in the transaction and also return a unique resource reference for that compensator:
+
+http://<machine>/jfdi-recovery-coordinator/<RecCoordId>
+
+Performing a GET on that URL will return the original compensator URL.
+Performing a PUT on that URL will overwrite the old compensator URL with the new one supplied.
+
+Performing a DELETE or POST will return a 401.
+
+When making an invocation on a resource that needs to participate in a transaction, the transaction context (URL) needs to be transmitted to the resource. How this happens is outside the scope of this effort. It may occur as additional payload on the initial request, or it may be that the client sends the context out-of-band to the resource.
+
+Once a resource has the transaction URL, it can register participation in the transaction (enlist the compensator). The compensator is free to use whatever URL structure it desires for uniquely identifying itself. The compensator URL must be unique for the transaction as well: the same compensator cannot be involved in more than one transaction. The compensator must support the following operations:
+
+Performing a GET on the participant URL will return the current status of the compensator, or 404 if the compensator is no longer present.
+
+The following types are returned by Compensators to indicate the current status:
+•    Compensating: the Compensator is currently compensating for the jfdi.
+•    Compensated: the Compensator has successfully compensated for the jfdi.
+•    FailedToCompensate: the Compensator was not able to compensate for the jfdi. It must maintain information about the work it was to compensate until the coordinator sends it a forget message.
+•    Completing: the Compensator is tidying up after being told to complete.
+•    Completed: the coordinator/participant has confirmed.
+•    FailedToComplete: the Compensator was unable to tidy-up.
+
+Performing a POST on <URL>/compensate will cause the participant to compensate the work that was done within the scope of the transaction. Performing a POST on <URL>/complete will cause the participant to tidy up and it can forget this transaction. In either case the compensator will either return a 200 OK code or a URL which indicates the outcome. That URL can be probed (via GET) and will simply return the same (implicit) information:
+
+<URL>/cannot-compensate
+<URL>/cannot-complete
+
+If the compensator is unknown (the URL is invalid) then 410 will be returned. It can be assumed by the coordinator that the service compensated.
+
+Note, a Compensator that cannot compensate must maintain its information until it is told to forget via POST <URL>/forget
+
+Performing a GET on <URL>/compensate will return 400.
+
+Performing a PUT on <URL>/compensate will return 400.
+
+It is expected that the receipt of cannot-compensate or cannot-complete will be handled by the application or logged if not.
+
+Note, a Compensator can resign from the jfdi at any time prior to the completion of an activity by performing a PUT on /jfdi-coordinator/<TxId>/remove with the URL of the compensator.
+
+When a Compensator is enrolled with an jfdi, the entity performing the enrol can supply a number of qualifiers which may be used by the coordinator and business application to influence the overall outcome of the activity. The currently supported qualifiers are:
+•    TimeLimit: the time limit (in seconds) that the Compensator can guarantee that it can compensate the work performed by the service. After this time period has elapsed, it may no longer be possible to undo the work within the scope of this (or any enclosing) jfdi. It may therefore be necessary for the application or service to start other activities to explicitly try to compensate this work. The application or coordinator may use this information to control the lifecycle of a jfdi.
+

Modified: labs/jbosstm/workspace/resttx/docs/readme.txt
===================================================================
--- labs/jbosstm/workspace/resttx/docs/readme.txt	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/docs/readme.txt	2009-02-01 12:05:44 UTC (rev 25035)
@@ -4,16 +4,23 @@
 mvn jetty6:run-exploded # run the coordinator and particpants
 ant # runs a client of the coordinator
 
-To debug the code:
+Notes:
 
-export MAVEN_OPTS="-Xdebug -Xnoagent
--Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5001
--Dmaven.test.skip=true"
+1. To debug the code:
+export MAVEN_OPTS="-Xdebug -Xnoagent -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5001 -Dmaven.test.skip=[true|false]"
 
 mvn -DforkMode=none test 
 
-To integrate with JBossTM set the following property:
+2. To integrate with JBossTM set the following property:
 
 <property name="com.arjuna.ats.internal.arjuna.inventory.staticInventoryImple.RESTRecord"
           value="org.jboss.jbossts.rts.resource.RESTRecordSetup" />
 
+The distribution comes with its own jbossjta-properties.xml with this property
+already set.
+
+3. The resource handlers are singletons. Thus, for example, if the coordinator
+and participant are in the same VM and then if the coordinator sends a request
+to the participant which sends a request back to the coordinator then they
+will deadlock.
+

Modified: labs/jbosstm/workspace/resttx/jbossjta-properties.xml
===================================================================
--- labs/jbosstm/workspace/resttx/jbossjta-properties.xml	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/jbossjta-properties.xml	2009-02-01 12:05:44 UTC (rev 25035)
@@ -48,7 +48,7 @@
         default is under user.home - must be writeable!)
       -->
         <property
-            name="com.arjuna.ats.arjuna.objectstore.objectStoreDir" value="PutObjectStoreHere" />
+            name="com.arjuna.ats.arjuna.objectstore.objectStoreDir" value="ObjectStore" />
         <property name="com.arjuna.ats.arjuna.coordinator.transactionLog" value="OFF"/>
         <!--
         (default is ON)

Modified: labs/jbosstm/workspace/resttx/pom.xml
===================================================================
--- labs/jbosstm/workspace/resttx/pom.xml	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/pom.xml	2009-02-01 12:05:44 UTC (rev 25035)
@@ -71,7 +71,10 @@
       <dependency>
          <groupId>org.jboss.resteasy</groupId>
          <artifactId>resteasy-jaxrs</artifactId>
+         <!--
          <version>1.0-beta-8</version>
+         -->
+         <version>1.0-RC1</version>
 
          <!-- filter out unwanted jars -->
          <exclusions>
@@ -91,6 +94,11 @@
             </exclusion>
          </exclusions>
       </dependency>
+              <dependency>
+                          <groupId>org.jboss.resteasy</groupId>
+                          <artifactId>resteasy-jaxb-provider</artifactId>
+                          <version>1.0-RC1</version>
+              </dependency>
    </dependencies>
 
    <build>
@@ -107,13 +115,17 @@
                <connectors>
                   <connector
                           implementation="org.mortbay.jetty.nio.SelectChannelConnector">
-                     <port>9095</port>
+                     <port>9099</port>
                      <host>localhost</host>
                      <maxIdleTime>60000</maxIdleTime>
                   </connector>
                </connectors>
                <systemProperties>
                   <systemProperty>
+                     <name>PORT</name>
+                     <value>9099</value>
+                  </systemProperty>
+                  <systemProperty>
                      <name>log4j.configuration</name>
                      <value>file:./src/test/resources/log4j.xml</value>
                   </systemProperty>

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/client/TxTest.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/client/TxTest.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/client/TxTest.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -22,15 +22,12 @@
 
 import org.jboss.resteasy.util.HttpResponseCodes;
 import org.jboss.jbossts.rts.util.TxUtil;
-import org.apache.commons.httpclient.HttpClient;
 import org.apache.log4j.Logger;
 
 import java.io.IOException;
 
-import junit.framework.Assert;
-
 /**
- * TODO
+ * Basic remote test. @see org.jboss.jbossts.rts.test.AppTest for more tests
  */
 public class TxTest
 {
@@ -38,77 +35,17 @@
 
     public static void main(String[] args) throws IOException
     {
-        modifyResource(2);
-    }
+        log.info("Base URI: " + TxUtil.getBaseURI());
+        String query = "clientId=12345&timeout=0";
+        String participant = TxUtil.getBaseURI() + "participant/work/pid1";
+        // start a transaction
+        String tx = TxUtil.doPost(new int[] {HttpResponseCodes.SC_CREATED}, TxUtil.txURI + "/begin?" + query);
 
-    private static void modifyResource(int txCnt) throws IOException
-    {
-        String[] txUrls = new String[txCnt];
-        String status;
+        // modify a resource (the example participant has been written such taht is will enlist itself with the transaction
+        TxUtil.doPost(new int[] {HttpResponseCodes.SC_OK}, participant,
+                "name=p1", "value=v1", "context=" + tx);
 
-        log.debug("Modifying resource ...");
-        String[] partUrls = {
-                TxUtil.getBaseURI() + "participant/work/p1",
-                TxUtil.getBaseURI() + "participant/work/p2",
-                TxUtil.getBaseURI() + "participant/work/p3",
-                TxUtil.getBaseURI() + "participant/work/p4"
-        };
-        String value = TxUtil.doGet(HttpResponseCodes.SC_OK, partUrls[0], "name=prop1");
-
-        log.debug("resouce value (no tx): " + value);
-
-        log.debug("starting a new transaction: " + TxUtil.txURI + "/begin/12345");
-        txUrls[0] = TxUtil.doPost(HttpResponseCodes.SC_CREATED, TxUtil.txURI + "/begin/12345");
-
-
-        // get the value of the resource
-        value = TxUtil.doGet(HttpResponseCodes.SC_OK, partUrls[0], "name=prop1");
-        log.debug("resouce value (in tx): " + value);
-        // modify the resource (with implicit enlistment)
-        TxUtil.doPost(HttpResponseCodes.SC_OK, partUrls[0], "name=prop1", "value=prop1 new value", "context=" + txUrls[0], "fault=move_to=x");
-        TxUtil.doPost(HttpResponseCodes.SC_OK, partUrls[1], "name=prop2", "value=prop2 new value", "context=" + txUrls[0], "fault=xcommit_fail");
-
-    // *********** start a second tx
-        if (txCnt > 1)
-        {
-            txUrls[1] = TxUtil.doPost(HttpResponseCodes.SC_CREATED, TxUtil.txURI + "/begin/1234567");
-            TxUtil.doPost(HttpResponseCodes.SC_OK, partUrls[2], "name=prop3", "value=prop3 new value", "context=" + txUrls[1]);
-            TxUtil.doPost(HttpResponseCodes.SC_OK, partUrls[3], "name=prop4", "value=prop4 new value", "context=" + txUrls[1]);
-        }
-    //  ************
-        // implicitly enlist a participant
-        log.debug("modifing within tx");
-        value = TxUtil.doGet(HttpResponseCodes.SC_OK, partUrls[0], "name=prop1", "context=" + txUrls[0]);
-        // get its status
-//        status = t.doGet(client, HttpResponseCodes.SC_OK, partUrls[0], "name=prop1", "context=" + txUrls[0]);
-
-//        log.debug("participant status: " + status);
-        log.debug("resouce value (in tx): " + value);
-
-        // prepare via participant
-//        value = t.doPost(client, HttpResponseCodes.SC_CREATED, TxUtil.getBaseURI() + "participant/1/prepare", "context=" + txUrl);
-//        TxUtil.log("prepare url: ", value);
-
-        // list transactions
-        String txns = TxUtil.doGet(HttpResponseCodes.SC_OK, TxUtil.txURI);
-        log.info(txns);
-
-        // commit via coordinator
-        log.debug("commiting " + txUrls[0]);
-        TxUtil.doPut(HttpResponseCodes.SC_OK, txUrls[0] + "/commit", "fault=xcommit_halt");
-
-        // get its status
-        log.debug("looking up status of " + txUrls[0]);
-        status = TxUtil.doGet(HttpResponseCodes.SC_OK, txUrls[0]);
-        log.info("Status: " + status);
-
-        if (txCnt > 1)
-            TxUtil.doPut(HttpResponseCodes.SC_OK, txUrls[1] + "/commit", "fault=xcommit_halt");
-
-        // list transactions
-        txns = TxUtil.doGet(HttpResponseCodes.SC_OK, TxUtil.txURI);
-        log.info(txns);
+        // commit the changes
+        TxUtil.doPut(new int[] {HttpResponseCodes.SC_OK}, tx + "/commit", "fault=xcommit_halt");
     }
-
-
 }

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/example/Participant.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/example/Participant.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/example/Participant.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -33,83 +33,216 @@
 import java.util.*;
 
 /**
- * TODO
+ * Participants are responsible for doing work on real resources and for obeying a 2PC protocol.
+ * The interface is defined by TransactionalParticipant.
+ * Participant URLs are unique for each transaction but is unconstrained. To satisfy this restriction
+ * we choose encode the transaction id into the url:
+ *         participant{pId}/tx/{tid}
+ * where pId is a participant id and tid is the transaction id (actually only tid would do).
  */
 @Path("/participant")
-public class Participant implements TransactionalParticipant
+public class Participant //implements TransactionalParticipant
 {
     protected final static Logger log = Logger.getLogger(Participant.class);
 
+    /*
+     * Define methods for spec conformance
+     */
+
+    /**
+     * Performing a GET on the participant URL will return the current status
+     * of the participant, or 404 if the participant is no longer present.
+     */
     @GET
-    @Path("{pId}/tx/{tid}")
-    public String getStatus(@PathParam("pId")String pid, @PathParam("tid") @DefaultValue("")String tid)
+    @Path("{pId}/tx/{tid}/{status}")
+    public Response getStatus(@PathParam("pId")String pid,
+                              @PathParam("tid") @DefaultValue("")String tid,
+                              @PathParam("status") String status)
     {
-        log.debug("particpant: status: " + pid + "/tx/" + tid);
-        return getWork(pid, tid).getStatus();
+        log.trace("particpant: status: " + pid + "/tx/" + tid);
+        return  TxUtil.response(
+            TxUtil.getBaseURI() + getWork(HttpResponseCodes.SC_NOT_FOUND, pid, TxUtil.txURI + "/" + tid).getStatus());
     }
 
+    /**
+     * Performing a POST on <P-URL>/prepare causes the participant to prepare
+     * the (implicitly) associated transaction. It returns a URL if
+     * successful, which indicates the outcome. That URL can be probed (via
+     * GET see getStatus()) and will simply return the same (implicit) information:
+     *
+     * <P-URL>/prepare-ok
+     * <P-URL>/prepare-readonly
+     * <P-URL>/prepare-notok
+     */
     @POST
     @Path("{pId}/tx/{tid}/prepare/")
-    public Response prepare(@PathParam("pId")String pid, @PathParam("tid") @DefaultValue("")String tid)
+    public Response prepare(@PathParam("pId")String pid,
+                               @PathParam("tid") @DefaultValue("")String tid)
     {
         log.debug("particpant: prepare: " + pid + "/tx/" + tid + "/prepare");
-        return TxUtil.postResponse(getWork(pid, TxUtil.txURI + "/" + tid).prepare());
+        return TxUtil.postResponse(getWork(HttpResponseCodes.SC_NOT_FOUND, pid, TxUtil.txURI + "/" + tid).prepare());
     }
+    @GET
+    @Path("{pId}/tx/{tid}/prepare/")
+    public Response getPrepare(@PathParam("pId")String pid,
+                               @PathParam("tid") @DefaultValue("")String tid)
+    {
+        return TxUtil.response(HttpResponseCodes.SC_NOT_FOUND);
+    }
+    @PUT
+    @Path("{pId}/tx/{tid}/prepare/")
+    public Response putPrepare(@PathParam("pId")String pid,
+                               @PathParam("tid") @DefaultValue("")String tid)
+    {
+        return TxUtil.response(HttpResponseCodes.SC_UNAUTHORIZED);
+    }
 
+    /**
+     * Performing a POST on <P-URL>/commit will cause the participant to commit
+     * on behalf of the transaction. It will return a URL if successful, which
+     * indicates the outcome. That URL can be probed (via GET) and will simply
+     * return the same (implicit) information:
+     *
+     * <P-URL>/committed
+     * <P-URL>/rolledback
+     * <P-URL>/heuristic
+     */
     @POST
     @Path("{pId}/tx/{tid}/commit/")
-    public Response commit(@PathParam("pId")String pid, @PathParam("tid") @DefaultValue("")String tid)
+    public Response commit(@PathParam("pId")String pid,
+                           @PathParam("tid") @DefaultValue("")String tid)
     {
         log.debug("particpant: commit: " + pid + "/tx/" + tid + "/commit");
 
         if (tid == null)
             throw new TransactionStatusException("no transaction");
 
-        if (!work.containsKey(pid + '/' + tid))   // resource must have moved
-            return TxUtil.response(HttpResponseCodes.SC_NOT_FOUND);
+	// get the work done on the resource in this transaction
+        Work w = getWork(HttpResponseCodes.SC_NOT_FOUND, pid, TxUtil.txURI + "/" + tid);
 
-        Work w = getWork(pid, TxUtil.txURI + "/" + tid);
+	// and update the original resource with the changes
+        return TxUtil.postResponse(w.commit(getWork(0, pid, "")));
+    }
+    @GET
+    @Path("{pId}/tx/{tid}/commit/")
+    public Response getCommit(@PathParam("pId")String pid,
+                              @PathParam("tid") @DefaultValue("")String tid)
+    {
+        return TxUtil.response(HttpResponseCodes.SC_NOT_FOUND);
+    }
+    @PUT
+    @Path("{pId}/tx/{tid}/commit/")
+    public Response putCommit(@PathParam("pId")String pid,
+                              @PathParam("tid") @DefaultValue("")String tid)
+    {
+        return TxUtil.response(HttpResponseCodes.SC_UNAUTHORIZED);
+    }
 
-        return TxUtil.postResponse(w.commit(getWork(pid, "")));
-    }
+    /**
+     * Performing a POST on <P-URL>/commit will cause the participant to commit
+     * on behalf of the transaction. It will return a URL if successful, which
+     * indicates the outcome. That URL can be probed (via GET) and will simply
+     * return the same (implicit) information:
+     *
+     * <P-URL>/committed
+     * <P-URL>/rolledback
+     * <P-URL>/heuristic<blah>
+     *
+     */
     @POST
     @Path("{pId}/tx/{tid}/rollback/")
-    public Response rollback(@PathParam("pId")String pid, @PathParam("tid") @DefaultValue("")String tid)
+    public Response rollback(@PathParam("pId")String pid,
+                             @PathParam("tid") @DefaultValue("")String tid)
     {
         log.debug("particpant: rollback: " + pid + "/tx/" + tid + "/rollback");
-        return TxUtil.postResponse(getWork(pid, TxUtil.txURI + "/" + tid).rollback());
+        return TxUtil.postResponse(getWork(HttpResponseCodes.SC_NOT_FOUND, pid, TxUtil.txURI + "/" + tid).rollback());
     }
+    @GET
+    @Path("{pId}/tx/{tid}/rollback/")
+    public Response getRollback(@PathParam("pId")String pid,
+                                @PathParam("tid") @DefaultValue("")String tid)
+    {
+        return TxUtil.response(HttpResponseCodes.SC_NOT_FOUND);
+    }
+    @PUT
+    @Path("{pId}/tx/{tid}/rollback/")
+    public Response putRollback(@PathParam("pId")String pid,
+                                @PathParam("tid") @DefaultValue("")String tid)
+    {
+        return TxUtil.response(HttpResponseCodes.SC_UNAUTHORIZED);
+    }
 
+    /**
+     * Performing a POST on <P-URL>/forget will cause the participant to forget
+     * any heuristic decision it made on behalf of the transaction.
+     */
     @POST
     @Path("{pId}/tx/{tid}/forget/")
-    public Response forget(@PathParam("pId")String pid, @PathParam("tid") @DefaultValue("")String tid)
+    public Response forget(@PathParam("pId")String pid,
+                           @PathParam("tid") @DefaultValue("")String tid)
     {
-        log.debug("particpant: forget: " + pid + "/tx/" + tid + "/forget");
-        return TxUtil.postResponse(getWork(pid, TxUtil.txURI + "/" + tid).forget());
+        log.debug("POST: particpant: forget: " + pid + "/tx/" + tid + "/forget");
+        return TxUtil.postResponse(getWork(HttpResponseCodes.SC_NOT_FOUND, pid, TxUtil.txURI + "/" + tid).forget());
     }
 
-    // define example urls for getting and modifying the resource
+    /**
+     * Performing a GET on <P-URL>/forget will return an indication of the
+     * heuristic if it still exists, or 404 if it does not.
+     */
+    @GET
+    @Path("{pId}/tx/{tid}/forget/")
+    public Response getForget(@PathParam("pId")String pid,
+                              @PathParam("tid") @DefaultValue("")String tid)
+    {
+        log.debug("GET: particpant: forget: " + pid + "/tx/" + tid + "/forget");
+        Work w = getWork(HttpResponseCodes.SC_NOT_FOUND, pid, TxUtil.txURI + "/" + tid);
 
+        if (w == null || w.getStatus() != Work.HEURISTIC)
+                return TxUtil.response(HttpResponseCodes.SC_NOT_FOUND); 
+
+        return getStatus(pid, tid, null);
+    }
+    @PUT
+    @Path("{pId}/tx/{tid}/forget/")
+    public Response putForget(@PathParam("pId")String pid,
+                              @PathParam("tid") @DefaultValue("")String tid)
+    {
+        return TxUtil.response(HttpResponseCodes.SC_UNAUTHORIZED ); 
+    }
+
+    /*
+     * Now define some resources specific to the example - namely urls
+     * for retrieving and modifying the resource
+     * 
+     * These methods are for simulating real resources to test that the 
+     * protocol is implemented correctly
+     */
+
     @POST
     @Path("work/{pId}")
-    public String modifyResource(@PathParam("pId")String pid, @QueryParam("context") @DefaultValue("")String ctx,
-                                 @QueryParam("name") String name, @QueryParam("value") String value, @QueryParam("fault") String fault)
+    public String modifyResource(@PathParam("pId")String pid,
+                                 @QueryParam("context") @DefaultValue("")String ctx,
+                                 @QueryParam("name") String name,
+                                 @QueryParam("value") String value,
+                                 @QueryParam("fault") String fault)
     {
-        log.debug("particpant: modify " + pid + " ctx: " + ctx + " fault: " + fault);
-        getWork(pid, ctx).put(name, value);
+        log.trace("particpant: modify " + pid + " ctx: " + ctx + " fault: " + fault);
+        getWork(0, pid, ctx).put(name, value);
         
         if (fault != null)
-            getWork(pid, ctx).put("fault", fault);
+            getWork(0, pid, ctx).put("fault", fault);
 
         return value;
     }
 
     @GET
     @Path("work/{pId}")
-    public String getResource(@PathParam("pId")String pid, @QueryParam("name") String name, @QueryParam("context") @DefaultValue("") String ctx)
+    public Response getResource(@PathParam("pId")String pid,
+                                @QueryParam("name") String name,
+                                @QueryParam("context") @DefaultValue("") String ctx)
     {
-        log.debug("particpant: get: work/" + pid + " - ctx: " + ctx);
-        return getWork(pid, ctx).get(name);
+        log.trace("particpant: get: work/" + pid + " - ctx: " + ctx);
+        return TxUtil.response(getWork(0, pid, ctx).get(name));
     }
 
     void removeParticipant(Work w)
@@ -117,9 +250,12 @@
         work.remove(w.getId());
     }
 
+    /**
+     * Tell the recovery sub-system that this participant has moved
+     */ 
     void moveParticipant(Work w, String newUrl)
     {
-        log.info("particpant: moving participant " + w.pid + " to " + newUrl);
+        log.trace("particpant: moving participant " + w.pid + " to " + newUrl);
         Work newWork = new Work(w);
 
         work.remove(w.getId());
@@ -134,7 +270,7 @@
 
         try
         {
-            TxUtil.doPut(HttpResponseCodes.SC_OK, TxUtil.rcURI + "/" + w.recCoordId, "URL=" + url);
+            TxUtil.doPut(new int[] {HttpResponseCodes.SC_OK}, TxUtil.rcURI + "/" + w.recCoordId, "URL=" + url);
         }
         catch (HttpResponseException e)
         {
@@ -148,22 +284,32 @@
         return TxUtil.getBaseURI() + new StringBuilder("participant/").append(w.pid).append("/tx/").append(w.ctx).toString().replaceAll("//", "/");
     }
 
-    private Work getWork(String pid, String ctx)
+    /**
+     * Get the work done by this participant on behalf of the given transaction
+     * If the work does not exist and the parameter statusCode is 0 then one is created
+     * otherwise an HttpResponseException is thrown filled in with the value of statusCode.
+     * The thrown exception will be mapped back to the required HTTP status code by the JAX RS
+     * implementation.
+     */
+    private Work getWork(int statusCode, String pid, String ctx) throws HttpResponseException
     {
         assert ctx != null;
         assert pid != null;
-        log.debug("getWork(" + pid + ", " + ctx + ')');
+        log.trace("getWork(" + pid + ", " + ctx + ')');
         int i = ctx.indexOf(TxUtil.txURI);
         if (i == -1 || i + TxUtil.txURI.length() == ctx.length())
             ctx = "";
         else
             ctx = ctx.substring(i + TxUtil.txURI.length());
-        
+
         String id = pid + ctx;
         Work w = work.get(id);
 
         if (w == null)
         {
+            if (statusCode != 0)
+                throw new HttpResponseException(HttpResponseCodes.SC_OK, statusCode);
+
             w = new Work(this, pid, ctx);
 
             if (ctx.length() != 0)
@@ -172,12 +318,15 @@
                 {
                     // enlist as a participant in this transaction context
                     String pUrl = makeParticipantUrl(w);
-                    w.recCoordId = TxUtil.doPut(HttpResponseCodes.SC_OK, TxUtil.txURI + ctx, "URL=" + pUrl);
+                    w.recCoordId = TxUtil.doPut(new int[] {HttpResponseCodes.SC_OK}, TxUtil.txURI + ctx, "URL=" + pUrl);
                 }
                 catch (HttpResponseException e)
                 {
                     throw new TMUnavailableException("Unable to contact TM: " + e.getMessage());
                 }
+                catch (Throwable t) {
+                    log.info("don't know what this is");
+                }
             }
 
             work.put(id, w);
@@ -188,6 +337,11 @@
 
     public Participant()
     {
+        /*
+         * We want to remove completed work. We could remove it immediately but the spec says that status
+         * urls can be probed (for an indeterminate period of time). So leave completed participants lying
+         * around for a limited period before culling them.
+         */
         timer = new Timer();
 
         int delay = 5000;   // delay for 5 sec.

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/example/Work.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/example/Work.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/example/Work.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -27,11 +27,16 @@
 import java.util.HashMap;
 
 /**
- * TODO
+ * Holder for isolating work done by participants from each other
+ * Simulate work done inside a transaction. Its only purpose is to
+ * show that work:
+ * - can be isolated from other work;
+ * - that is committed really does create new state;
+ * - that is rolled back really does abort any changes;
  */
 public class Work
 {
-    String pid; // = "participant/" + ++id;
+    String pid;
     String ctx;
     String recCoordId;
     Participant bean;
@@ -72,8 +77,7 @@
 
     public String getStatus()
     {
-//            if (ctx == null) return "notx";
-        return pid + "/participant/" + status;
+        return "participant/" + pid + "/tx" + ctx + '/' + status;
     }
 
     public String setStatus(String status)
@@ -96,6 +100,10 @@
     static final String COMMIT_OK = "commit-ok";
     static final String HEURISTIC = "heuristic";
 
+    /**
+     * This method enables the tests to validate that any new state was
+     * aborted.
+     */
     public String rollback()
     {
         validateRequest(false);
@@ -104,6 +112,13 @@
         return setStatus(ROLLEDBACK); // heuristic
     }
 
+    /**
+     * This work can now be made persistent so update the original work
+     * (identified by mergeTo) with the contents of this work.
+     *
+     * This method enables the tests to validate that any new state was
+     * committed.
+     */
     public String commit(Work mergeTo)
     {
         validateRequest(false);
@@ -120,8 +135,8 @@
     {
         validateRequest(false);
         bean.removeParticipant(this);
-        
-        return setStatus(ROLLEDBACK);
+
+        return getStatus();
     }
 
     private void validateRequest(boolean prepare)

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/HttpResponseException.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/HttpResponseException.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/HttpResponseException.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -21,7 +21,7 @@
  package org.jboss.jbossts.rts.provider;
 
 /**
- * TODO
+ * Exception for wrapping unexpected http response codes
  */
 public class HttpResponseException extends Error
 {
@@ -37,6 +37,11 @@
         this.actualResponse = actualResponse;
     }
 
+    public HttpResponseException(int expectedResponse, int actualResponse)
+    {
+        this(null, null, expectedResponse, actualResponse);
+    }
+
     public int getExpectedResponse()
     {
         return expectedResponse;

Added: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/HttpResponseMapper.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/HttpResponseMapper.java	                        (rev 0)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/HttpResponseMapper.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -0,0 +1,39 @@
+/*
+ * JBoss, Home of Professional Open Source
+ * Copyright 2009, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags.
+ * See the copyright.txt in the distribution for a
+ * full listing of individual contributors.
+ * This copyrighted material is made available to anyone wishing to use,
+ * modify, copy, or redistribute it subject to the terms and conditions
+ * of the GNU Lesser General Public License, v. 2.1.
+ * This program is distributed in the hope that it will be useful, but WITHOUT A
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+ * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
+ * You should have received a copy of the GNU Lesser General Public License,
+ * v.2.1 along with this distribution; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA  02110-1301, USA.
+ *
+ * (C) 2008
+ * @author JBoss Inc.
+ */
+package org.jboss.jbossts.rts.provider;
+
+import org.jboss.resteasy.util.HttpResponseCodes;
+
+import javax.ws.rs.ext.Provider;
+import javax.ws.rs.ext.ExceptionMapper;
+import javax.ws.rs.core.Response;
+
+/**
+ * Maps an HttpResponseException to the HTTP code embedded in the exception
+ */
+ at Provider
+public class HttpResponseMapper implements ExceptionMapper<HttpResponseException>
+{
+   public Response toResponse(HttpResponseException exception)
+   {
+      return Response.status(exception.getActualResponse()).build();
+   }
+}

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/NotFoundMapper.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/NotFoundMapper.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/NotFoundMapper.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -27,7 +27,7 @@
 import javax.ws.rs.core.Response;
 
 /**
- * TODO
+ * 404 mapper
  */
 @Provider
 public class NotFoundMapper implements ExceptionMapper<ResourceNotFoundException>

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/ResourceNotFoundException.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/ResourceNotFoundException.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/ResourceNotFoundException.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -21,7 +21,7 @@
  package org.jboss.jbossts.rts.provider;
 
 /**
- * TODO
+ * 404 exception
  */
 public class ResourceNotFoundException extends RuntimeException
 {

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TMUnavailableException.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TMUnavailableException.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TMUnavailableException.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -21,7 +21,7 @@
  package org.jboss.jbossts.rts.provider;
 
 /**
- * TODO
+ * Unable to contact the REST based Transaction Coordinator
  */
 public class TMUnavailableException extends RuntimeException
 {

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TMUnavailableMapper.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TMUnavailableMapper.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TMUnavailableMapper.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -27,7 +27,7 @@
 import javax.ws.rs.core.Response;
 
 /**
- * TODO
+ * Map service unavailable exceptions
  */
 @Provider
 public class TMUnavailableMapper implements ExceptionMapper<TMUnavailableException>
@@ -36,4 +36,4 @@
    {
       return Response.status(HttpResponseCodes.SC_SERVICE_UNAVAILABLE).build();
    }
-}
\ No newline at end of file
+}

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TransactionStatusException.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TransactionStatusException.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TransactionStatusException.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -21,7 +21,7 @@
  package org.jboss.jbossts.rts.provider;
 
 /**
- * TODO
+ * transaction status exception
  */
 public class TransactionStatusException extends RuntimeException
 {

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TransactionStatusMapper.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TransactionStatusMapper.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/provider/TransactionStatusMapper.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -27,7 +27,7 @@
 import javax.ws.rs.core.Response;
 
 /**
- * TODO
+ * map transaction status exceptions
  */
 @Provider
 public class TransactionStatusMapper implements ExceptionMapper<TransactionStatusException>

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/resource/RESTRecord.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/resource/RESTRecord.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/resource/RESTRecord.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -35,7 +35,7 @@
 import org.apache.log4j.Logger;
 
 /**
- * TODO
+ * Log record for driving participants through 2PC and recoverery
  */
 public class RESTRecord extends AbstractRecord
 {
@@ -49,7 +49,7 @@
     public RESTRecord(String url, String txId)
     {
         super(new Uid());
-        log.debug("RESTRecord url: " + url + " tx: " + txId);
+        log.trace("RESTRecord url: " + url + " tx: " + txId);
         this.url = url;
         this.txId = txId;
         coordinatorID = get_uid().fileStringForm();
@@ -57,13 +57,13 @@
 
     public static AbstractRecord create()
     {
-        log.debug("RESTRecord.create");
+        log.trace("RESTRecord.create");
         return new RESTRecord();
     }
     public RESTRecord()
     {
         super();
-        log.debug("RESTRecord()");
+        log.trace("RESTRecord()");
     }
 
     protected RESTRecord(Uid u)
@@ -110,8 +110,12 @@
 
     public int topLevelPrepare()
     {
-        if (fault.equals(Fault.prepare_halt))
+        log.debug("prepare " + url);
+
+        if (fault.equals(Fault.prepare_halt)) {
+            log.info("prepare: halt VM");
             Runtime.getRuntime().halt(1);
+        }
         
         if (fault.equals(Fault.h_hazard))
             return TwoPhaseOutcome.HEURISTIC_HAZARD;
@@ -122,8 +126,8 @@
         try
         {
             String txUrl = TxUtil.txURI + '/' + txId;
-            log.debug("preparing " + url);
-            this.statusUrl = TxUtil.doPost(HttpResponseCodes.SC_CREATED, url + "/prepare", "context=" + txUrl);
+            this.statusUrl = TxUtil.doPost(new int[] {HttpResponseCodes.SC_CREATED}, url + "/prepare", "context=" + txUrl);
+
             prepared = true;
             return TwoPhaseOutcome.PREPARE_OK;
         }
@@ -138,18 +142,33 @@
 
     public int topLevelAbort()
     {
-        if (fault.equals(Fault.abort_halt))
+        log.debug("abort " + url);
+
+        if (fault.equals(Fault.abort_halt)) {
+            log.info("abort: halt VM");
             Runtime.getRuntime().halt(1);
+        }
 
-        // TODO
         if (url == null || txId == null)
             return TwoPhaseOutcome.FINISH_ERROR;
 
-        return TwoPhaseOutcome.FINISH_OK;
+        try {
+            statusUrl = TxUtil.doPost(new int[] {HttpResponseCodes.SC_CREATED}, this.url + "/rollback");
+
+            return TwoPhaseOutcome.FINISH_OK;
+        } catch (HttpResponseException e) {
+            if (log.isInfoEnabled())
+                log.info("participant " + this.url + " rollback error: " + e.getMessage());
+            this.statusUrl = this.url + "/heuristic"; // don't know what status the participant is in
+
+            return TwoPhaseOutcome.FINISH_ERROR;
+        }
     }
 
     public int topLevelCommit()
     {
+        log.debug("commit " + url);
+
         if (url == null || txId == null)
             return TwoPhaseOutcome.PREPARE_READONLY;
 
@@ -173,9 +192,11 @@
 
     public int topLevelOnePhaseCommit()
     {
-        if (fault.equals(Fault.commit_halt))
+        if (fault.equals(Fault.commit_halt)) {
+            log.info("commit: halt VM");
             Runtime.getRuntime().halt(1);
-        
+        }
+   
         if (txId == null)
             return TwoPhaseOutcome.FINISH_ERROR;
 
@@ -183,9 +204,9 @@
 
         try
         {
-            log.debug("commiting " + this.url);
+            if (log.isDebugEnabled()) log.debug("commiting " + this.url);
             if (!statusUrl.endsWith("prepare-readonly"))
-                statusUrl = TxUtil.doPost(HttpResponseCodes.SC_CREATED, this.url + "/commit"); //, "context=" + txUrl));
+                statusUrl = TxUtil.doPost(new int[] {HttpResponseCodes.SC_CREATED}, this.url + "/commit"); //, "context=" + txUrl));
 
             return TwoPhaseOutcome.FINISH_OK;
         }
@@ -196,10 +217,11 @@
                 // the participant may have moved so check the coordinator url
                 if (hasParticpantMoved())
                 {
-                    log.debug("participant has moved commit to new url " + this.url);
+                    if (log.isDebugEnabled())
+                        log.debug("participant has moved commit to new url " + this.url);
                     try
                     {
-                        this.statusUrl = TxUtil.doPost(HttpResponseCodes.SC_CREATED, this.url + "/commit"); //, "context=" + txUrl));
+                        this.statusUrl = TxUtil.doPost(new int[] {HttpResponseCodes.SC_CREATED}, this.url + "/commit"); //, "context=" + txUrl));
 
                         return TwoPhaseOutcome.FINISH_OK;
                     }
@@ -210,23 +232,42 @@
                 }
             }
 
-            log.info("participant " + this.url + " commit error: " + e.getMessage());
+            if (log.isInfoEnabled())
+                log.debug("participant " + this.url + " commit error: " + e.getMessage());
             this.statusUrl = this.url + "/commit-notok";
 
             return TwoPhaseOutcome.FINISH_ERROR;
         }
     }
 
+    /**
+     * A participant tells the coordinator if it changes its URL.
+     * To see if this has happened perform a GET on the recovery url which returns the
+     * last known location of the participant.
+     */
     private boolean hasParticpantMoved()
     {
-        String url = TxUtil.doGet(HttpResponseCodes.SC_OK, TxUtil.getBaseURI() + "recovery-coordinator/" + coordinatorID);
-
-        if (url != null && !url.equals(this.url))
+        try
         {
-            this.url = url;
+            if (log.isDebugEnabled())
+                log.trace("seeing if participant has moved: " + TxUtil.getBaseURI() + "recovery-coordinator/" + coordinatorID);
 
-            return true;
+            // get the latest participant url by probing the recovery url:
+            String url = TxUtil.doGet(new int[] {HttpResponseCodes.SC_OK}, TxUtil.getBaseURI() + "recovery-coordinator/" + coordinatorID);
+
+            if (url != null && !url.equals(this.url))
+            {
+                // participant has moved so remember the new location
+                this.url = url;
+
+            	if (log.isDebugEnabled())
+                	log.trace("\tnew url is " + url);
+                return true;
+            }
         }
+        catch (HttpResponseException e)
+        {
+        }
 
         return false;
     }
@@ -260,6 +301,7 @@
             url = os.unpackString();
             coordinatorID = os.unpackString();
             statusUrl = os.unpackString();
+            log.info("restore_state " + url);
 
             return super.restore_state(os, t);
         }
@@ -321,6 +363,8 @@
         {
             if (f.name().equals(name))
             {
+                log.trace("setFault: " + f + " url: " + url);
+
                 fault = f;
                 return;
             }

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/resource/RESTRecordSetup.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/resource/RESTRecordSetup.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/resource/RESTRecordSetup.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -25,7 +25,7 @@
 import com.arjuna.ats.arjuna.gandiva.ObjectName;
 
 /**
- * TODO
+ * Setup for RESTRecord
  */
 public class RESTRecordSetup implements InventoryElement
 {

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/service/Coordinator.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/service/Coordinator.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/service/Coordinator.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -39,10 +39,6 @@
 import com.arjuna.ats.arjuna.AtomicAction;
 import com.arjuna.ats.arjuna.common.Uid;
 
-/**
- * The transaction coordinator URL is:
- *  http://<machine>/transaction-coordinator
- */
 @Path("/")
 public class Coordinator
 {
@@ -62,7 +58,7 @@
     @Produces("application/json")
     public TransactionList getAllTransactions()
     {
-        log.debug("coordinator: list: transaction-coordinator");
+        log.trace("coordinator: list: transaction-coordinator");
         return getTransactions(null);
     }
 
@@ -77,7 +73,7 @@
     @Produces("application/json")
     public TransactionList getTransactionStatus(@PathParam("id") String id)
     {
-        log.debug("coordinator: status: transaction-coordinator/" + id);
+        log.trace("coordinator: status: transaction-coordinator/" + id);
         final Uid uid = new Uid(id);
         TransactionFilter statusFilter = new TransactionFilter() {
             public boolean accept(Transaction t)
@@ -94,7 +90,7 @@
     @Produces("text/html")
     public Response getAllTransactionsText() throws Exception
     {
-        log.debug("coordinator: html list");
+        log.trace("coordinator: html list");
         TransactionList listing = getTransactions(null);
 
         return Response.ok(TxUtil.stringFormat(listing)).build();
@@ -109,7 +105,7 @@
     @Produces("application/json")
     public TransactionList getRecoveringTransactions()
     {
-        log.debug("coordinator: transaction-coordinator/recovery");
+        log.trace("coordinator: transaction-coordinator/recovery");
         return getTransactions(recoveringFilter);
     }
 
@@ -122,7 +118,7 @@
     @Produces("application/json")
     public TransactionList getActiveTransactions()
     {
-        log.debug("coordinator: transaction-coordinator/active");
+        log.trace("coordinator: transaction-coordinator/active");
         return getTransactions(activeTxFilter);
     }
     /**
@@ -134,7 +130,7 @@
 //    @RolesAllowed(value = "NONE")
     public Response deleteTransaction(@PathParam("id") String id)
     {
-        log.debug("coordinator: delete: transaction-coordinator/" + id);
+        log.trace("coordinator: delete: transaction-coordinator/" + id);
         return TxUtil.response(HttpResponseCodes.SC_UNAUTHORIZED);
     }
 
@@ -146,10 +142,12 @@
      * @return a <URL> of the form /transaction-coordinator/<TxId>
      */
     @POST
-    @Path("transaction-coordinator/begin/{clientId}")
-    public Response beginTransaction(@PathParam("clientId")String clientId, @QueryParam("timeout") @DefaultValue("0")int timeout)
+    @Path("transaction-coordinator/begin/")
+    public Response beginTransaction(@QueryParam("clientId")String clientId, @QueryParam("timeout") @DefaultValue("0")int timeout)
+//    @Path("transaction-coordinator/begin/{clientId}")
+//    public Response beginTransaction(@PathParam("clientId")String clientId, @QueryParam("timeout") @DefaultValue("0")int timeout)
     {
-        log.debug("coordinator: begin: transaction-coordinator/begin/" + clientId + "?timeout=" + timeout);
+        log.trace("coordinator: begin: transaction-coordinator/begin/" + clientId + "?timeout=" + timeout);
         Transaction tx = new Transaction(clientId);
         String uid = tx.get_uid().fileStringForm();
 
@@ -187,7 +185,7 @@
         tx.setFault(fault);
         AtomicAction.resume(tx);
         int res = tx.commit(true);
-        log.debug("commit result: " + res);
+        log.trace("commit result: " + res);
 
         if (tx.isTransactionInMidFlight())
         {
@@ -201,6 +199,30 @@
         return TxUtil.response(HttpResponseCodes.SC_OK);
     }
 
+    @PUT
+    @Path("transaction-coordinator/{TxId}/rollback")
+    public Response rollbackTransaction(@PathParam("TxId")String txId, @QueryParam("fault") @DefaultValue("")String fault)
+    {
+        log.debug("coordinator: rollback: transaction-coordinator/" + txId + "/rollback");
+        Transaction tx = getTransaction(txId);
+
+        tx.setFault(fault);
+        AtomicAction.resume(tx);
+        int res = tx.abort();
+        log.trace("rollback result: " + res);
+
+        if (tx.isTransactionInMidFlight())
+        {
+            AtomicAction.suspend();
+            throw new TransactionStatusException("Transaction failed to rollback: ");
+        }
+
+        if (!tx.hasHeuristic())
+            transactions.remove(txId);
+
+        return TxUtil.response(HttpResponseCodes.SC_OK);
+    }
+
     /**
      * Register a participant in a tx
      * @param txId id of transaction
@@ -211,21 +233,20 @@
     @Path("transaction-coordinator/{TxId}")
     public Response enlistParticipant(@PathParam("TxId")String txId, @QueryParam("URL")String url)
     {
-        log.debug("coordinator: enlist: transaction-coordinator/" + txId + "?URL=" + url);
         Transaction tx = getTransaction(txId);
         String coordinatorId = tx.enlistParticipant(url);
 
         if (coordinatorId == null)
             return TxUtil.response(HttpResponseCodes.SC_NOT_ACCEPTABLE);
 
-        log.debug("... enlisting participant " + url + " in tx " + txId);
+        log.debug("enlisting participant " + url + " in tx " + txId + " Coordinator url: " + TxUtil.rcURI + '/' + coordinatorId);
+        // TODO would be nice if there was something in the spec for telling the participant it
+        // has been enlisted
         participants.put(coordinatorId, url);
 
-        String prefix = ""; // "http://<machine>/recovery-coordinator/"
-        return Response.ok(coordinatorId).build();
-//        return prefix + coordinatorId;
+        return Response.ok(TxUtil.rcURI + '/' + coordinatorId).build();
     }
-
+         
     /**
      * Get the participant url (registered during enlistParticipant) corresponding to a resource reference
      * if the coordinator crashes - the participant list will be empty but this is ok if commit hasn't been
@@ -237,7 +258,7 @@
     @Path("recovery-coordinator/{RecCoordId}")
     public Response lookupParticipant(@PathParam("RecCoordId")String enlistmentId)
     {
-        log.debug("coordinator: lookup: transaction-coordinator/" + enlistmentId);
+        log.trace("coordinator: lookup: transaction-coordinator/" + enlistmentId);
 
         String p = participants.get(enlistmentId);
 
@@ -248,7 +269,8 @@
     }
 
     /**
-     * PUT /recovery-coordinator/<RecCoordId>/<new participant URL>  - overwrite the old <participant URL> with <new participant URL>
+     * PUT /recovery-coordinator/<RecCoordId>/<new participant URL> -
+     *   overwrite the old <participant URL> with <new participant URL>
      *   (as with JTS, this will also trigger off a recovery attempt on the associated transaction)
      * A participant may use this url to notifiy the coordinator that he has moved to a new location.
      * @param url
@@ -257,7 +279,7 @@
     @Path("recovery-coordinator/{RecCoordId}")
     public Response replaceParticipant(@PathParam("RecCoordId")String enlistmentId, @QueryParam("URL")String url)
     {
-        log.debug("coordinator: replace: recovery-coordinator/" + enlistmentId + "?URL=" + url);
+        log.trace("coordinator: replace: recovery-coordinator/" + enlistmentId + "?URL=" + url);
         participants.put(enlistmentId, url);
 
         return TxUtil.response(HttpResponseCodes.SC_OK);
@@ -267,7 +289,7 @@
     @Path("recovery-coordinator/{RecCoordId}")
     public Response postParticipant(@PathParam("RecCoordId")String enlistmentId)
     {
-        log.debug("coordinator: replace via Post: recovery-coordinator/" + enlistmentId);
+        log.trace("coordinator: replace via Post: recovery-coordinator/" + enlistmentId);
         return Response.status(HttpResponseCodes.SC_UNAUTHORIZED).build();
     }
 
@@ -275,7 +297,7 @@
     @Path("recovery-coordinator/{RecCoordId}")
     public Response deleteParticipant(@PathParam("RecCoordId")String enlistmentId)
     {
-        log.debug("coordinator: replace vi Delete: recovery-coordinator/" + enlistmentId);
+        log.trace("coordinator: replace vi Delete: recovery-coordinator/" + enlistmentId);
         return TxUtil.response(HttpResponseCodes.SC_UNAUTHORIZED);
     }
 

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/service/TMApplication.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/service/TMApplication.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/service/TMApplication.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -13,6 +13,7 @@
 import org.jboss.jbossts.rts.provider.NotFoundMapper;
 import org.jboss.jbossts.rts.provider.TMUnavailableMapper;
 import org.jboss.jbossts.rts.provider.TransactionStatusMapper;
+import org.jboss.jbossts.rts.provider.HttpResponseMapper;
 
 public class TMApplication extends Application
 {
@@ -62,7 +63,8 @@
     private static Class<?>[] mappers = {
         NotFoundMapper.class,
         TMUnavailableMapper.class,
-        TransactionStatusMapper.class
+        TransactionStatusMapper.class,
+        HttpResponseMapper.class
     };
     
     private static Object[] resources = {

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/service/TransactionalParticipant.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/service/TransactionalParticipant.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/service/TransactionalParticipant.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -22,26 +22,31 @@
  * @author JBoss Inc.
  */
 
+/**
+ * Not used. This interface shows the methods that a participant must implement.
+ * However the methods constrain the form that a participant url must take and
+ * is therefore inappropriate (since they ought to be unconstrained).
+ */
 public interface TransactionalParticipant
 {
     @GET
     @Path("{pId}/tx/{tid}")
-    String getStatus(@PathParam("pId")String pid, @QueryParam("context") String ctx);
+    Response getStatus(@PathParam("pId")String pid, @QueryParam("context") String ctx, @PathParam("status") String status);
 
     @POST
-    @Path("{pId}/tx/transaction-coordinator/{tid}/prepare/")
+    @Path("{pId}/tx/{tid}/prepare/")
     Response prepare(@PathParam("pId")String pid, @PathParam("tid")String tid);
 
     @POST
-    @Path("{pId}/tx/transaction-coordinator/{tid}/commit/")
+    @Path("{pId}/tx/{tid}/commit/")
     Response commit(@PathParam("pId")String pid, @PathParam("tid")String tid);
 
     @POST
-    @Path("{pId}/tx/transaction-coordinator/{tid}/rollback/")
+    @Path("{pId}/tx/{tid}/rollback/")
     Response rollback(@PathParam("pId")String pid, @PathParam("tid")String tid);
 
     @POST
-    @Path("{pId}/tx/transaction-coordinator/{tid}/forget/")
+    @Path("{pId}/tx/{tid}/forget/")
     Response forget(@PathParam("pId")String pid, @PathParam("tid")String tid);    
 
 // don't forget about one phase commit

Modified: labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/util/TxUtil.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/util/TxUtil.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/main/java/org/jboss/jbossts/rts/util/TxUtil.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -40,7 +40,7 @@
 import javax.ws.rs.core.Response;
 
 /**
- * TODO
+ * Various utilites for sending HTTP messages
  */
 public class TxUtil
 {
@@ -51,7 +51,7 @@
     
     private static HttpClient client = new HttpClient(new MultiThreadedHttpConnectionManager());
 
-    private static int serverPort = 9095;
+    private static int serverPort = getIntProperty("PORT", "9096");
     private static String serverHost = "localhost";
     private static String serverContext = "tx/";
     private static String baseURI = "http://" + serverHost + ":" + serverPort + "/" + serverContext;
@@ -59,12 +59,27 @@
     public static String txURI = TxUtil.getBaseURI() + TX_URI_PREFIX;
     public static String rcURI = TxUtil.getBaseURI() + RC_URI_PREFIX;
 
+    public static int getIntProperty(String name, String def)
+    {
+        String port = System.getProperty("PORT", def);
+
+        try {
+            return Integer.valueOf(port);
+        } catch (NumberFormatException e) {
+            try {
+                return Integer.valueOf(def);
+            } catch (NumberFormatException e2) {
+                throw new IllegalArgumentException("invalid default value for server port: " + def, e2);
+            }
+        }
+    }
+
     private static void updateEndpoint()
     {
         baseURI = "http://" + serverHost + ":" + serverPort + "/" + serverContext;
         txURI = baseURI + TX_URI_PREFIX;
         rcURI = baseURI + RC_URI_PREFIX;
-        log.info("new baseURI: " + baseURI);
+        log.info("base URI: " + baseURI);
     }
 
     public static void setPort(int port)
@@ -97,11 +112,12 @@
     {
         return baseURI;
     }
-    
-    public static String doMethod(int expect, HttpMethod method, String ... nvParams) throws HttpResponseException
+
+    public static String doMethod(int[] expect, HttpMethod method, String ... nvParams) throws HttpResponseException
     {
-//        log.debug("Method: " + method.getName() + " " + method.getURI().toString() + " expect response: " + expect);
+//        log.trace("Method: " + method.getName() + " " + method.getURI().toString() + " expect response: " + expect);
         int status = -1;
+        int ex = expect.length == 0 ? -1 : expect[0];
 
         if (nvParams.length != 0)
         {
@@ -110,7 +126,7 @@
             for (int i = 0; i < nvParams.length; i++)
             {
                 String[] pair = nvParams[i].split("=");
-                if (expect != -1)
+                if (expect.length == 0)
                     assert pair.length == 2;
                 params[i] = new NameValuePair(pair[0], pair[1]);
             }
@@ -120,17 +136,20 @@
 
         try
         {
-            log.info(method.getName() + ": " + method.getURI());
+            log.trace(method.getName() + ": " + method.getURI());
             status = client.executeMethod(method);
 
-            if (expect > 0 && expect != status)
-                throw new HttpResponseException(null, getResponseBody(method), expect, status);
+            for (int i : expect)
+                if (i == status) {
+                    return getResponseBody(method);
+                }
 
-            return new String(method.getResponseBody(), "US-ASCII");
+            throw new HttpResponseException(null, getResponseBody(method), ex, status);
+
         }
         catch (IOException e)
         {
-            throw new HttpResponseException(e, "", expect, status);
+            throw new HttpResponseException(e, "", ex, status);
         }
         finally
         {
@@ -142,20 +161,23 @@
     {
         try
         {
+            if (method.getResponseBody() == null)
+                return method.getStatusLine().toString();
+
             return new String(method.getResponseBody(), "US-ASCII");
         }
         catch (IOException e)
         {
-            return "";
+            return e.getMessage();
         }
     }
 
-    public static String doGet(int expect, String url, String... nvParams) throws HttpResponseException
+    public static String doGet(int[] expect, String url, String... nvParams) throws HttpResponseException
     {
         return doMethod(expect, new GetMethod(url), nvParams);
     }
 
-    public static String doPost(int expect, String url, String... nvParams) throws HttpResponseException
+    public static String doPost(int[] expect, String url, String... nvParams) throws HttpResponseException
     {
         HttpMethod method = new PostMethod(url);
         String res = doMethod(expect, method, nvParams);
@@ -164,12 +186,12 @@
         return header != null ? header.getValue() : res;
     }
 
-    public static String doPut(int expect, String url, String... nvParams) throws HttpResponseException
+    public static String doPut(int[] expect, String url, String... nvParams) throws HttpResponseException
     {
         return doMethod(expect, new PutMethod(url), nvParams);
     }
 
-    public static String doDelete(int expect, String url, String... nvParams) throws HttpResponseException
+    public static String doDelete(int[] expect, String url, String... nvParams) throws HttpResponseException
     {
         return doMethod(expect, new DeleteMethod(url), nvParams);
     }
@@ -200,4 +222,14 @@
     {
         return Response.status(code).build();
     }
+
+    public static Response response(String body)
+    {
+        if (body == null || body.length() == 0)
+            return TxUtil.response(HttpResponseCodes.SC_NO_CONTENT);
+
+        Response.ResponseBuilder builder = Response.ok(body);
+
+        return builder.build();
+    }
 }

Modified: labs/jbosstm/workspace/resttx/src/test/java/org/jboss/jbossts/rts/test/AppTest.java
===================================================================
--- labs/jbosstm/workspace/resttx/src/test/java/org/jboss/jbossts/rts/test/AppTest.java	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/test/java/org/jboss/jbossts/rts/test/AppTest.java	2009-02-01 12:05:44 UTC (rev 25035)
@@ -6,18 +6,18 @@
 import org.jboss.resteasy.util.HttpResponseCodes;
 import org.jboss.jbossts.rts.util.TxUtil;
 import org.jboss.jbossts.rts.service.TMApplication;
-import org.apache.commons.httpclient.HttpClient;
+import org.jboss.jbossts.rts.provider.HttpResponseException;
 import org.apache.log4j.Logger;
 
 import java.io.IOException;
 
 /**
- * TODO
+ * Run through some basic tests to validate that the protocol matches the specification
  */
 public class AppTest extends TestCase
 {
     protected final static Logger log = Logger.getLogger(AppTest.class);
-    private static int PORT = 9096;
+    private static int PORT = TxUtil.getIntProperty("PORT", "9096");
     private static TJWSEmbeddedJaxrsServer tjws;
 
     private String[] URLS;
@@ -38,14 +38,14 @@
 
         TxUtil.setEndpoint("localhost", PORT, "");
 
-        log.info("setUp: " + TxUtil.getBaseURI());
+        log.trace("setUp: " + TxUtil.getBaseURI());
         URLS = new String[] {
                 TxUtil.txURI + ".html", // list txns
-                TxUtil.txURI + "/begin/12345",   // start a tx on behalf of client id 12345
-                TxUtil.txURI + "/TX/commit",   // commit tx 123
+                TxUtil.txURI + "/begin?clientId=12345",   // start a tx on behalf of client id 12345
+                TxUtil.txURI + "/tx/commit",   // commit tx 123
                 TxUtil.txURI + "/0/commit", // commit a non existant tx
                 TxUtil.txURI + "/commit", // commit a non existant tx
-                TxUtil.txURI + "/TX",   // enlist a participant in tx 123
+                TxUtil.txURI + "/tx",   // enlist a participant in tx 123
         };
 
         tjws = new TJWSEmbeddedJaxrsServer();
@@ -58,157 +58,177 @@
 
     protected void tearDown() throws Exception
     {
-        log.info("stoppig server");
+        log.trace("stoppig server");
         super.tearDown();
 
         tjws.stop();
     }
 
-    public void testTx() throws IOException
+    private String startTx(long timeout, boolean fail) throws IOException
     {
-        Integer[] opts = {0,1,2,3,4,5};
-        String tx;
+        String tx = null;
 
-        for (Integer opt : opts)
-        {
-            log.info("Testing url " + URLS[opt]);
+        try {
+            String query = "clientId=12345" + (timeout == 0 ? "" : "&timeout=" + timeout);
 
-            switch (opt)
-            {
-                case 0:
-                    // list transactons
-                    TxUtil.doGet(HttpResponseCodes.SC_OK, TxUtil.txURI + ".html");
-                    break;
-                case 1:
-                    // start a tx on behalf of client id 12345
-                    tx = TxUtil.doPost(HttpResponseCodes.SC_CREATED, TxUtil.txURI + "/begin/12345");
-                    log.info("Started tx " + tx);
-                    log.info("Commiting tx " + tx);
-                    TxUtil.doPut(HttpResponseCodes.SC_OK, tx + "/commit");
-                    log.info("Commiting tx " + tx + "/commit");
-                    TxUtil.doPut(HttpResponseCodes.SC_NOT_FOUND, tx + "/commit");
-                    break;
-                case 2:
-                    break;
-                case 3:
-                    // commit a non existant tx
-                    TxUtil.doPost(HttpResponseCodes.SC_NOT_FOUND, TxUtil.txURI + "/0/commit");
-                    break;
-                case 4:
-                    // commit a non existant tx
-                    TxUtil.doPost(HttpResponseCodes.SC_METHOD_NOT_ALLOWED, TxUtil.txURI + "/commit");
-                    break;
-                case 5:
-                    // start a tx on behalf of client id 12345
-                    tx = TxUtil.doPost(HttpResponseCodes.SC_CREATED, TxUtil.txURI + "/begin/12345");
-                    // enlist a participant
-                    log.info("Started tx " + tx + " ... enlisting");
-                    String pUrl = TxUtil.getBaseURI() + "particpant/123";
-                    String recCoordId = TxUtil.doPut(HttpResponseCodes.SC_OK, tx, "URL=" + pUrl);
-                    // commit the tx
-                    TxUtil.doPut(HttpResponseCodes.SC_OK, tx + "/commit");
-                    break;
-                default:
-            }
+            tx = TxUtil.doPost(new int[] {HttpResponseCodes.SC_CREATED}, TxUtil.txURI + "/begin?" + query);
+            log.trace("Started tx: " + tx);
+            if (fail)
+                assertTrue("start tx should have failed", false);
+        } catch (HttpResponseException e) {
+            if (!fail)
+                assertTrue(e.getMessage(), false);
         }
+
+        return tx;
     }
 
-    public void testParticipants() throws IOException
+    private String modifyResource(String tx, String pid, String name, String value)
     {
-        int txCnt = 2;
-        String[] txUrls = new String[txCnt];
-        String status;
+        try {
+            return TxUtil.doPost(new int[] {HttpResponseCodes.SC_OK}, TxUtil.getBaseURI() + "participant/work/" + pid,
+                    "name=" + name, "value=" + value, "context=" + tx);
+        } catch (HttpResponseException e) {
+            assertTrue(e.getMessage(), false);
+            return null;
+        }
+    }
 
-        log.debug("Modifying resource ...");
-        String[] partUrls = {
-                TxUtil.getBaseURI() + "participant/work/p1",
-                TxUtil.getBaseURI() + "participant/work/p2",
-                TxUtil.getBaseURI() + "participant/work/p3",
-                TxUtil.getBaseURI() + "participant/work/p4"
-        };
-        String value = TxUtil.doGet(HttpResponseCodes.SC_OK, partUrls[0], "name=prop1");
+    private String getResource(String tx, String pid, String name)
+    {
+        try {
+            return TxUtil.doGet(new int[] {HttpResponseCodes.SC_OK}, TxUtil.getBaseURI() + "participant/work/" + pid,
+                    "name=" + name, "context=" + tx);
+        } catch (HttpResponseException e) {
+            assertTrue(e.getMessage(), false);
+            return null;
+        }
+    }
 
-        log.debug("resouce value (no tx): " + value);
+    private void terminateTx(String tx, String how, boolean fail, String ... args)
+    {
+        try {
+            TxUtil.doPut(new int[] {HttpResponseCodes.SC_OK}, tx + "/" + how, args);
+            if (fail)
+                assertTrue("commit should not have succeeded", false);
+        } catch (HttpResponseException e) {
+            if (!fail)
+                assertTrue(e.getMessage(), false);
+	}
+    }
 
-        log.debug("starting a new transaction");
-        txUrls[0] = TxUtil.doPost(HttpResponseCodes.SC_CREATED, TxUtil.txURI + "/begin/12345");
+    private void commitTx(String tx, boolean fail, String ... args)
+    {
+        terminateTx(tx, "commit", fail, args);
+    }
 
+    private void rollbackTx(String tx, boolean fail, String ... args)
+    {
+        terminateTx(tx, "rollback", fail, args);
+    }
 
-        // get the value of the resource
-        value = TxUtil.doGet(HttpResponseCodes.SC_OK, partUrls[0], "name=prop1");
-        log.debug("resouce value (in tx): " + value);
-        // modify the resource (with implicit enlistment)
-        TxUtil.doPost(HttpResponseCodes.SC_OK, partUrls[0], "name=prop1", "value=prop1 new value", "context=" + txUrls[0]);
-        TxUtil.doPost(HttpResponseCodes.SC_OK, partUrls[1], "name=prop2", "value=prop2 new value", "context=" + txUrls[0], "fault=xcommit_fail");
+    // 1PC commit
+    public void xtest1() throws IOException
+    {
+        String tx = startTx(0L, false);
+        modifyResource(tx, "pid1", "p1", "v1");
+        commitTx(tx, false);
+    }
+    // 2PC commit
+    public void xtest2() throws IOException
+    {
+        String tx = startTx(0L, false);
+        modifyResource(tx, "pid1", "p1", "v1");
+        modifyResource(tx, "pid2", "p2", "v2");
+        commitTx(tx, false);
+        commitTx(tx, true);	// should not be able to commit a completed transaction
+    }
+    // commit an invalid transaction
+    public void xtest3() throws IOException
+    {
+        String tx = "http://localhost:9096/transaction-coordinator/dead_7f000001_b9cc_4981ded5_5";
+        commitTx(tx, true);
+    }
 
-    // *********** start a second tx
-        if (txCnt > 1)
-        {
-            txUrls[1] = TxUtil.doPost(HttpResponseCodes.SC_CREATED, TxUtil.txURI + "/begin/1234567");
-            TxUtil.doPost(HttpResponseCodes.SC_OK, partUrls[2], "name=prop3", "value=prop3 new value", "context=" + txUrls[1]);
-            TxUtil.doPost(HttpResponseCodes.SC_OK, partUrls[3], "name=prop4", "value=prop4 new value", "context=" + txUrls[1]);
-        }
-    //  ************
-        // implicitly enlist a participant
-        log.debug("modifing within tx");
-        value = TxUtil.doGet(HttpResponseCodes.SC_OK, partUrls[0], "name=prop1", "context=" + txUrls[0]);
-        // get its status
-//        status = t.doGet(client, HttpResponseCodes.SC_OK, partUrls[0], "name=prop1", "context=" + txUrls[0]);
+    /**
+     * Modify a resource within a transaction and rollback.
+     * Ensure that the resource has not changed.
+     */
+    public void xtest4() throws IOException
+    {
+        String v1 = modifyResource(null, "pid1", "p1", "v1");
+        String v2 = modifyResource(null, "pid2", "p1", "v1");
 
-//        log.debug("participant status: " + status);
-        log.debug("resouce value (in tx): " + value);
+        String tx = startTx(0L, false);
 
-        // prepare via participant
-//        value = t.doPost(client, HttpResponseCodes.SC_CREATED, "participant/1/prepare", "context=" + txUrl);
-//        TxUtil.log("prepare url: ", value);
+        String v3 = modifyResource(tx, "pid1", "p1", "txv1");
+        String v4 = modifyResource(tx, "pid2", "p1", "txv1");
 
-        // list transactions
-        String txns = TxUtil.doGet(HttpResponseCodes.SC_OK, TxUtil.txURI);
-        log.info(txns);
+        rollbackTx(tx, false);
+        rollbackTx(tx, true);	// should be able to rollback a completed transaction
 
-        // commit via coordinator
-        TxUtil.doPut(HttpResponseCodes.SC_OK, txUrls[0] + "/commit", "fault=xcommit_halt");
+        // make sure the changes were rolled back
+        String v5 = getResource(null, "pid1", "p1");
+        String v6 = getResource(null, "pid2", "p1");
 
-        // get its status
-        status = TxUtil.doGet(HttpResponseCodes.SC_OK, txUrls[0]);
-        log.info("Status: " + status);
-
-        if (txCnt > 1)
-            TxUtil.doPut(HttpResponseCodes.SC_OK, txUrls[1] + "/commit", "fault=xcommit_halt");
-
-        // list transactions
-        txns = TxUtil.doGet(HttpResponseCodes.SC_OK, TxUtil.txURI);
-        log.info(txns);
+        assertTrue("participant 1's work wasn't rolled back correctly", v1.equals(v5));
+        assertTrue("participant 2's work wasn't rolled back correctly", v2.equals(v6));
     }
 
-    public void testParticipant() throws IOException
+    /**
+     * Test the Recovery URL
+     */
+    public void xtest5() throws IOException
     {
-        log.debug("starting a new transaction");
-        String txUrl = TxUtil.doPost(HttpResponseCodes.SC_CREATED, URLS[1]);
+        String tx = startTx(0L, false);	// start a transaction
 
-        String pUrl = TxUtil.getBaseURI() + "particpants/123";
-        String getUrl = TxUtil.rcURI + '/';
-        // enlist url
-        log.debug("Enlisting " + txUrl + "?URL=" + pUrl + " with particpant url: " + pUrl);
-        String recCoordId = TxUtil.doPut(HttpResponseCodes.SC_OK, txUrl, "URL=" + pUrl);
-        // check that the url is correct
-        log.debug("Validating using url " + getUrl + recCoordId);
-        String origUrl = TxUtil.doGet(HttpResponseCodes.SC_OK, getUrl + recCoordId);
-        assertEquals(pUrl, origUrl);
+        String pUrl = TxUtil.getBaseURI() + "particpant/123";	// participant url
+	// Registering a participant with the transaction returns a recovery url
+        String recCoordId = TxUtil.doPut(new int[] {HttpResponseCodes.SC_OK}, tx, "URL=" + pUrl);
 
+	// doing a GET on recCoordId should return the original url
+        String origUrl = TxUtil.doGet(new int[] {HttpResponseCodes.SC_OK}, recCoordId);
+	assertEquals("GET on recovery url does not return orignal participant url", pUrl, origUrl);
+
+	// doing a PUT on recCoordId should replace the participant URL (used by
+	// participants to tell the recovery coordinator that they have moved
+	pUrl = TxUtil.getBaseURI() + "particpant/123"; // the new participant URL
         // replace the url with a new one
-        pUrl = TxUtil.getBaseURI() + "particpants/123";
-        log.debug("replacing with " + pUrl);
-        TxUtil.doPut(HttpResponseCodes.SC_OK, getUrl + recCoordId, "URL=" + pUrl);
+        TxUtil.doPut(new int[] {HttpResponseCodes.SC_OK}, recCoordId, "URL=" + pUrl);
 
-        // check that the url was replaced
-        log.debug("Checking that it was replaced");
-        origUrl = TxUtil.doGet(HttpResponseCodes.SC_OK, getUrl + recCoordId);
-        assertEquals(pUrl, origUrl);
+        // check that the url was replaced by doing another GET on the recovery URL
+        origUrl = TxUtil.doGet(new int[] {HttpResponseCodes.SC_OK}, recCoordId);
+        assertEquals("GET on recovery url does not return the replaced participant url", pUrl, origUrl);
 
-        // POST and DELETE should fail
-        log.debug("checking POST and DELETE fail");
-        TxUtil.doPost(HttpResponseCodes.SC_UNAUTHORIZED, getUrl + recCoordId);
-        TxUtil.doDelete(HttpResponseCodes.SC_UNAUTHORIZED, getUrl + recCoordId);
+        // and finally check that POST and DELETE on the recovery URL fail
+        TxUtil.doPost(new int[] {HttpResponseCodes.SC_UNAUTHORIZED}, recCoordId);
+        TxUtil.doDelete(new int[] {HttpResponseCodes.SC_UNAUTHORIZED}, recCoordId);
     }
+
+    /*
+     * Test recovery
+     */
+    public void test6() throws IOException
+    {
+        boolean crashVM = false;
+        String[] txns = {startTx(0L, false), startTx(0L, false)};
+
+        for (int i = 0; i < txns.length; i++) {
+            modifyResource(txns[i], "pid1", "p1", "v1" + i);
+            modifyResource(txns[i], "pid2", "p2", "v2" + i);
+        }
+
+        // list transactions
+        String txlist = TxUtil.doGet(new int[] {HttpResponseCodes.SC_OK}, TxUtil.txURI);
+        // there should be 2
+        assertEquals("List transactions does not match the number of running transactions",
+            txlist.split("ActionStatus").length - 1, 2);
+
+        for (int i = 0; i < txns.length; i++) {
+            if (i == txns.length - 1 && crashVM)
+            	commitTx(txns[i], false, "fault=commit_halt");
+            else
+            	commitTx(txns[i], false);
+        }
+    }
 }

Modified: labs/jbosstm/workspace/resttx/src/test/resources/log4j.xml
===================================================================
--- labs/jbosstm/workspace/resttx/src/test/resources/log4j.xml	2009-01-31 21:49:21 UTC (rev 25034)
+++ labs/jbosstm/workspace/resttx/src/test/resources/log4j.xml	2009-02-01 12:05:44 UTC (rev 25035)
@@ -5,7 +5,7 @@
 
     <appender name="console" class="org.apache.log4j.ConsoleAppender">
         <param name="Target" value="System.out"/>
-        <param name="Threshold" value="TRACE"/>
+        <param name="Threshold" value="INFO"/>
 
         <layout class="org.apache.log4j.PatternLayout">
             <param name="ConversionPattern" value="%d{ABSOLUTE} {%8.8t} (%x) [%-5p,%-10c{1}] %m%n"/>
@@ -15,7 +15,7 @@
     <appender name="file" class="org.apache.log4j.FileAppender">
         <param name="File" value="logs/test.log"/>
         <param name="Append" value="false"/>
-        <param name="Threshold" value="ERROR"/>
+        <param name="Threshold" value="INFO"/>
 
         <layout class="org.apache.log4j.PatternLayout">
             <param name="ConversionPattern" value="%d [%t] %p - %m%n"/>
@@ -27,15 +27,9 @@
         <appender-ref ref="console"/>
         <appender-ref ref="file"/>
     </category>
-<!--
-    <category name="org.jboss.jbossts">
-        <level value="WARN"/>
-        <appender-ref ref="console"/>
-        <appender-ref ref="file"/>
-    </category>
--->
+
     <category name="com.arjuna">
-        <level value="WARN"/>
+        <level value="INFO"/>
         <appender-ref ref="console"/>
         <appender-ref ref="file"/>
     </category>




More information about the jboss-svn-commits mailing list