5 Replies Latest reply on Mar 4, 2012 8:25 PM by rkilcoyne

    JTA spanning multiple RestEasy calls

    hipjon

      I just put together a prototype of a RestEasy transaction manager that hooks into the JBoss transaction manager. My goal was to allow multiple web service calls to be wrapped within a single JTA transaction. I wanted to make use of the built in JBoss transaction manager so that Hibernate and my backing EJBs would still participate in the transaction without custom code/configuration. Here's the class:

       

       

      {code:java}

      @Path("/tx")
      public  class TransactionResource {


           public static Map<String, Transaction> txCache =  Collections.synchronizedMap(new HashMap<String, Transaction>());

         
           @GET

           @Path("/start")

           @Produces("text/plain")

           public String startTX() {

                try {

                     TransactionManager tmgr = (TransactionManager)new  InitialContext().lookup("java:/TransactionManager");
                     tmgr.begin();

                     Transaction tx = tmgr.suspend();

                     String uuid = UUID.randomUUID().toString();

                     txCache.put(uuid, tx);

                     return uuid;
                } catch  (Throwable t) {
                     return ResponseXMLWriter.toXML(t,  "file/update");
                }  
          }


           @GET
           @Path("/commit/{id}")
           @Produces("text/plain")
           public  String commitTX(@PathParam("id") String txid) {
                try {
                     Transaction tx = txCache.get(txid);
                      if  (tx == null)
                          throw new  ValidationException("Transaction not found for id " + txid);          
                 
                     TransactionManager tmgr = (TransactionManager)new  InitialContext().lookup("java:/TransactionManager");
                     tmgr.resume(tx);
                

                     tmgr.commit();          
                 
                     return "COMMIT SUCCEEDED";
                } catch  (Throwable t) {
                     return ResponseXMLWriter.toXML(t,  "file/update");
                } finally {
                     txCache.remove(txid);
                }

          }

           @GET
           @Path("/rollback/{id}")
           @Produces("text/plain")
           public String rollbackTX(@PathParam("id") String txid) {
                try  {
                     Transaction tx = txCache.get(txid);
                
                     if (tx == null)
                          throw new  ValidationException("Transaction not found for id " + txid);
                 
                     TransactionManager tmgr = (TransactionManager)new  InitialContext().lookup("java:/TransactionManager");
                     tmgr.resume(tx);
                
                     tmgr.rollback();
                             
                     return "ROLLBACK SUCCEEDED";          
                }  catch (Throwable t) {
                     return ResponseXMLWriter.toXML(t,  "file/update");
                } finally {
                     txCache.remove(txid);
                }
           }

      }

       

      It's a simple prototype but it appears to work. I can call /tx/start to get back a transaction ID. I can then pass that ID to any other RestEasy call which will look up the Transaction from the cache and call TransactionManager.resume(tx) before calling my EJBs. Here's an example:

       

       

      {code:java}

      @GET
          @Path("/{id}/update")
          @Produces("text/plain")
           public String updateStatus(@PathParam("id") String id,  @QueryParam("status") String status, @QueryParam("tx") String txId) {
               try {
                  Transaction tx =  TransactionResource.txCache.get(txId);
                
                  if  (tx == null)
                      throw new  ValidationException("Transaction not found for id " + txId);          
                 
                  InitialContext ctx = new InitialContext();
                   TransactionManager tmgr =  (TransactionManager)ctx.lookup("java:/TransactionManager");
                   tmgr.resume(tx);
                
                  FileLookupBean bean =  (FileLookupBean)(ctx.lookup("FileLookupBean/no-interface"));
                   bean.updateStatus(Long.parseLong(id), status);
                
                   tmgr.suspend();
                
                  return "Updated file " +  id + "with status " + status;
                
              } catch  (Throwable t) {
                  return ResponseXMLWriter.toXML(t,  "file/update");
              }   


          }

      {code}

       

       

      The method FileLookupBean.updateStatus() simply updates one of my JPA classes. The updates are committed or rolled back correctly by calling /tx/commit/{txid} or /tx/rollback/{txid}


      While this all seems to work, I'm no expert regarding the inner workings of JTA or JBoss's implementation. I was wondering if anyone could see anything clearly broken with this prototype? I'm aware of the architectural concerns around long running transactions and obviously the txCache will need code to handle orphaned Transactions. However, is there anything technically flawed with what I'm doing here?


      Your input is appreciated!

        • 1. Re: JTA spanning multiple RestEasy calls
          mmusgrov

          Your solution looks good and should work fine provided there are no failures. As you intimate your txCache holder for live transactions does need to made resiliant to failures and for that you would need to hook into the JBossTS recovery susbsystem . We have a prototype that follows closely what you are doing that does handle recovery - if you want to check it out go to https://svn.jboss.org/repos/labs/labs/jbosstm/workspace/resttx

           

          Regards,

          Mike

          • 2. Re: JTA spanning multiple RestEasy calls
            hipjon

            Mike,

             

            Thanks for this! I'll have to educate myself on the recovery subsystem. I had a question about the way you are enlisting participants in the transaction. Are participants meant to be enlisted for every jax-rs call, or perhaps on a per-client basis? It's not obvious to me what functionality you're getting by creating multiple participants (i.e. RESTRecords) for a single transaction? Would you mind enlightening me? I'm no doubt in over my head here.

             

            Thanks!

            Jon

            • 3. Re: JTA spanning multiple RestEasy calls
              mmusgrov
              I had a question about the way you are enlisting participants in the transaction. Are participants meant to be enlisted for every jax-rs call, or perhaps on a per-client basis? It's not obvious to me what functionality you're getting by creating multiple participants (i.e. RESTRecords) for a single transaction?

               

               

              Your implementation caters for situations where all transactional work is carried out by JEE components (such as EJBs and JPA persistence units) running in the Application Server and, furthermore, only in the Application Server which started the transaction.

               

              In a REST system the types of clients and servers are not typically EJBs or database resource managers and can reside anywhere on the web, they are simply resources. To facilite this requirement we use the idea of a transaction participant.

               

              A participant in our system requires that the participant expose URLs that the transaction manager will use to drive the participant through prepare and commit. In this way an arbitary REST resource can take part in the traditional two phase commit transaction protocol along with conventional components such as EJBs.

               

              Our implementation uses "RESTRecords" to represent each of these participants. These records are the implementation mechanism for hooking into the JBossTS recovery sub-system. The recovery system scans a transaction log (which outlives the Applicaton Server or the JVM that added records to it - in our example we use Tomcat or Jetty containers) looking for outstanding records that have initially failed to complete the the second phase of the transaction protocol and attempts to replay the second phase on the record. So, in effect, the recovery system calls the commit method on the "RESTRecord" which then calls back to the original participant URL using JAX-RS.

               

              In this way if the transaction manager or the participant fail then recovery can still make progress.

              • 4. Re: JTA spanning multiple RestEasy calls
                hipjon

                That clears things up! Yes, my solution just needs to support the backing JEE components, though your example will be helpful in writing the recovery code. Thanks again!

                • 5. Re: JTA spanning multiple RestEasy calls
                  rkilcoyne

                  Jon, I see your original post was from April 2010. What did you end up doing to handle the propogation of Transactions across multiple REST calls?

                   

                  Rick