/*
 * JBossMQ, the OpenSource JMS implementation
 * 
 * Distributable under LGPL license. See terms of license at gnu.org.
 */
package org.jboss.mq.pm;

import javax.jms.JMSException;

import org.jboss.mq.ConnectionToken;
import org.jboss.mq.SpyJMSException;

import EDU.oswego.cs.dl.util.concurrent.ConcurrentHashMap;

/**
 * This class allows provides the base for user supplied persistence packages.
 * 
 * @author Hiram Chirino (Cojonudo14@hotmail.com)
 * @author Paul Kendall (paul.kendall@orion.co.nz)
 * @author <a href="mailto:adrian@jboss.org">Adrian Brock</a>
 * @version $Revision: 1.6.6.1 $
 */
public class TxManager
{
   // Constants -----------------------------------------------------
   
   // Attributes ----------------------------------------------------

   /** The persistence manager */
   PersistenceManager persistenceManager;
   
   /** Maps Global transactions to local transactions */
   ConcurrentHashMap globalToLocal = new ConcurrentHashMap();
   
   /**
    * Create a new TxManager
    *
    * @param pm the persistence manager
    */
   // Static --------------------------------------------------------
   
   // Constructors --------------------------------------------------

   public TxManager(PersistenceManager pm)
   {
      persistenceManager = pm;
   }
   
   // Public --------------------------------------------------------

   /**
     * Return the local transaction id for a distributed transaction id.
     * 
     * @param dc the connection
     * @param xid the transaction id
     * @return The Prepared transaction
     * @exception javax.jms.JMSException Description of Exception
     */
   public final Tx getPrepared(ConnectionToken dc, Object xid) throws JMSException
   {
      GlobalXID gxid = new GlobalXID(dc, xid);
      Tx txid = (Tx) globalToLocal.get(gxid);
      if (txid == null)
         throw new SpyJMSException("Transaction does not exist from: " + dc.getClientID() + " xid=" + xid);

      return txid;
   }

   /**
     * Create and return a unique transaction id.
     * 
     * @return the transaction id
     * @exception JMSException for any error
     */
   public final Tx createTx() throws JMSException
   {
      Tx txId = persistenceManager.createPersistentTx();
      return txId;
   }

   /**
     * Commit the transaction to the persistent store.
     * 
     * @param txId the transaction
     * @exception JMSException for any error
     */
   public final void commitTx(Tx txId) throws JMSException
   {
      txId.commit(persistenceManager);
   }

   /**
    * Add an operation for after a commit
    *
    * @param txId the transaction
    * @param task the task
    * @throws JMSException for any error
    */
   public void addPostCommitTask(Tx txId, Runnable task) throws JMSException
   {
      if (txId == null)
      {
         task.run();
         return;
      }

      txId.addPostCommitTask(task);
   }

   /**
     * Rollback the transaction.
     * 
     * @param txId the transaction
     * @exception JMSException for any error
     */
   public void rollbackTx(Tx txId) throws JMSException
   {
      txId.rollback(persistenceManager);
   }

   /**
    * Add an operation for after a rollback
    *
    * @param txId the transaction
    * @param task the task
    * @throws JMSException for any error
    */
   public void addPostRollbackTask(Tx txId, Runnable task) throws JMSException
   {
      if (txId == null)
         return;

      txId.addPostRollbackTask(task);
   }

   /**
     * Create and return a unique transaction id. Given a distributed connection
     * and a transaction id object, allocate a unique local transaction id if
     * the remote id is not already known.
     * 
     * @param dc the connection token
     * @param xid the xid
     * @return the transaction
     * @exception JMSException for any error
     */
   public Tx createTx(ConnectionToken dc, Object xid) throws JMSException
   {

      GlobalXID gxid = new GlobalXID(dc, xid);
      if (globalToLocal.containsKey(gxid))
         throw new SpyJMSException("Duplicate transaction from: " + dc.getClientID() + " xid=" + xid);

      Tx txId = createTx();
      globalToLocal.put(gxid, txId);

      //Tasks to remove the global to local mappings on commit/rollback
      txId.addPostCommitTask(gxid);
      txId.addPostRollbackTask(gxid);

      return txId;
   }
   
   // Package protected ---------------------------------------------
   
   // Protected -----------------------------------------------------
   
   // Private -------------------------------------------------------
   
   // Inner classes -------------------------------------------------
   
   /**
     * A global transaction
     */
   class GlobalXID implements Runnable
   {
      ConnectionToken dc;
      Object xid;

      GlobalXID(ConnectionToken dc, Object xid)
      {
         this.dc = dc;
         this.xid = xid;
      }

      public boolean equals(Object obj)
      {
         if (obj == null)
         {
            return false;
         }
         if (obj.getClass() != GlobalXID.class)
         {
            return false;
         }
         return ((GlobalXID) obj).xid.equals(xid) && ((GlobalXID) obj).dc.equals(dc);
      }

      public int hashCode()
      {
         return xid.hashCode();
      }

      public void run()
      {
         globalToLocal.remove(this);
      }
   }
}